PHPUnit Sandbox — зміна результату роботи класу «на льоту» в PHP 7.x

3 хв. читання

В продовження теми юніт тестування в PHP, хотілося б розкрити проблему залежності методів від зовнішніх сервісів (API, БД, інші класів тощо). TDD говорить про те, що правдивим тестом можна вважати той тест, на який не впливають зовнішні чинники.

Якщо ви пробували розібратися з TDD, то у вас, як і в кожного з нас, виникло питання: «А як тоді таке можна покрити тестами?».

Аналіз наявних рішень

Трохи погугливши можна знайти різного роді рішення, першим і найвідомішим з яких є RunKit — розширення ядра PHP версії 5.x.

Недоліки RunKit, на які особисто я звернув увагу:

  1. Це розширення для PHP ядра. Не знаю наскільки правильно корегувати роботу ядра під тестування коду.
  2. RunKit не працює на PHP 7.x.

По другому пункту в мережі є ряд рішень, в тому числі й форк RunKit, який намагається локалізувати його для PHP 7.x. \ На жаль, всі мої спроби встановити його в себе на localhost завершилися лише помилками й відсутністю результату. А писати на PHP 5.6 тільки заради можливості покрити більшу кількість коду тестами — не виглядає хорошою ідеєю.

В github issues, один з авторів першого RunKit Dmitry Zenovich назвав спробу локалізації розширення для PHP 7.x кастрацією :)

Моє розв'язання проблеми

Мені так і не вдалося знайти якесь хороше робоче рішення, яке б мені підійшло і я вирішив це по своєму, написавши бібліотеку PHPUnit Sandbox.

Основна ідея — реалізація пісочниці для обмеження області видимості оголошених класів. Це потрібно для того, щоб можна було оголошувати «на льоту» потрібні псевдо класи в середині пісочниці, перед тим, як це зробить autoloader.

Виглядає це так:

use PHPUnit\\Framework\\TestCase;
use WebPackage\\PHPUnitSandbox\\UnitSandbox;

require_once __DIR__ . '/PHPUnitSandbox/autoloader.php'; // Підключаємо автозавантажувач PHPUnit Sandbox

// Ініціалізуємо пісочницю
UnitSandbox::init(); //Для коректної роботи всі інші автозавантажувачі потрібно реєструвати через init() метод (див. README.md)
	->registerAutoloader();

class ExampleUnitTest extends TestCase
{
	/**
	 * Test Method mocking up
	 */
	public function testMockClass()
	{
		//Оголошуємо псевдо клас `MyNSpace\\DB` зі статичним методом `query()`, який має повернути власний екземпляр, і методом екземпляру `execute()`, який має повернути масив [1,2,3]
		UnitSandbox::mockClass('\\MyNSpace\\DB')
			->mockStaticMethod('query', UnitSandbox::SELF_INSTANCE)
			->mockMethod('execute', [1,2,3]);

		//Викликаємо оголошений псевдоклас в середині пісочниці
		$result_array = UnitSandbox::execute(function () {
			return \\MyNSpace\\DB::query()
				->execute();
		});

		UnitSandbox::cleanMockedData();

		$this->assertEquals([1,2,3], $result_array);
	}
}

В результаті, тест виконається без помилок, не зважаючи на те, чи взагалі існує клас \\MyNSpace\\DB.

Доступні можливості

Бібліотека вміє оголошувати «на льоту»:

  • псевдо класи,
  • методи та властивості екземпляра псевдо класів,
  • старичні методи та властивості псевдо класів,
  • константи псевдо класів,
  • класи-шпигуни, які наслідують потрібний клас і дозволяють змінити окремі дані/методи класу, не змінюючи всю його логіку.

PHPUnitSandbox сумісна зі всіма версіями PHP, починаючи від v5.4 і вище. \ Для її роботи необхідно всього лиш дозвіл на виконання функцій exec() і eval() — жодних додаткових налаштувань чи розширень ядра.

Чистого коду і попутних завдань!

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.8K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація