Верх страницы
Обложка к записи Модульное тестирование (Unit tests) с помощью PHPUnit
Время для прочтения: 1 мин. 36 сек.

Модульное тестирование (Unit tests) с помощью PHPUnit

Зачем нужны тесты и какие виды тестов должен писать разработчик вы можете узнать здесь.

Адриан Кун (Adrian Kuhn) и другие:

Модульные тесты главным образом пишутся в качестве хорошей практики, помогающей разработчикам выявлять и исправлять баги, проводить рефакторинг кода и служить в качестве документации для тестируемого программного модуля (программы).

Для достижения этих преимуществ модульные тесты в идеале должны охватывать все возможные пути исполнения программы. Один модульный тест обычно покрывает один конкретный путь в одной функции или методе. Однако тестовые методы необязательно должны быть инкапсулированными и независимыми.

Часто существуют неявные зависимости между тестовыми методами, скрытые в сценарии реализации теста.

Установка PHPUnit и написание первый тест

Для начала нужно установить версию php7.2+ и composer. Затем в проекте устанавливаем phpunit:

composer require --dev phpunit/phpunit

Создаем пример для теста Duck.php:

class Duck {

	public function say(): string {
		return 'krya-krya';
	}

}

Создаем папку tests и добавляем туда тестируем класс DuckTest.php (по умолчанию PHPUnit смотрит на все файлы в которые заканчиваются на Test.php).

require_once __DIR__ . '/../Duck.php';

class Krya_Test extends \PHPUnit\Framework\TestCase {

	public function test_say() {
		$krya = new Duck();
		$this->assertSame( 'krya-krya', $krya->say() );
	}

}

И запускаем тесты:

vendor/bin/phpunit tests/

Если вы увидели такое сообщение, то все сделано верно.

PHPUnit 9.0.1 by Sebastian Bergmann and contributors.

.     1 / 1 (100%)

Time: 42 ms, Memory: 4.00 MB

OK (1 test, 1 assertion)

Базовые вещи, которые нужно знать:

Фикстуры (Fixture)

Фиксутра — настройка окружения и возврат его в исходное состояние. Звучит страшно, но на самом деле это просто методы, которые позволяют настроить окружение для теста. В классе \PHPUnit\Framework\TestCase для этого есть методы:

  • setUp выполняются перед каждым тестом тестового класса;
  • tearDown выполняются после каждого теста тестового класса;
  • setUpBeforeClass — выполняется перед первым тестом в тестовом классе;
  • tearDownAfterClass — выполняется после запуска последнего теста тестового класса.

Пример:

class FixtureTest extends \PHPUnit\Framework\TestCase {

	private $counter;
	
	protected function setUp(): void {
		$this->counter = 0;
		parent::setUp();
	}

	public function test_fixture_1() {
		$this->counter++;
		$this->assertSame( 1, $this->counter );
	}

	public function test_fixture_2() {
		$this->assertSame( 0, $this->counter );
	}

}

Получается в каждом тестируемом методе свойство counter будет равно 0.

Утверждение (Asserts)

Утверждения — это методы класса TestCase, которые помогают проверить тест. Все эти методы начинаются с assert (assertTrue, assertSame, assertClassHasAttribute и т.д.). После выполнения тестов будет показано кол-во выполненных тестов и кол-во выполненных утверждений. В случае неверного утверждения тест считается проваленным.

Тестовые двойники (Заглушки, Stubs, Mocks)

Stub — объект, который заменяет реальный объект. Например у нас есть класс Duck в конструктор которого должен попасть объект Headdress:

use PHPUnit\Framework\TestCase;

class StubTest extends TestCase {
	public function testStub() {
		$headdress = $this->createMock(Headdress::class);
		$duck = new Duck( $headdress );

		$this->assertSame('foo', $duck->quack() );
	}
}

Mock — отличается от стаба тем, что еще описывает какое-то поведение и его изменение влияет на выполнение теста.

При тестировании одного класса в разных его методах один и тот же внешний объект может быть как моком, так и стабом.

use PHPUnit\Framework\TestCase;

class MockTest extends TestCase {
	public function testMock() {
    $headdress = $this->createMock(Headdress::class);
    $headdress
      ->expects( $this->once() ) // Ожидаем, что метод вызовется один раз.
      ->method( ‘back’ ); // Указываем, какой метод должен быть вызван.

		$duck = new Duck( $headdress );

		$this->assertTrue(  $duck->run() );
	}
}

В нашем примере мы еще описали, что ожидаем, что Duck при вызове метод run вызовет внутри себя метод back объекта Headdress.

Настройка окружения для всех тестов (Bootstrap)

Для настройки окружения для всех тестов можно создать файл bootstrap.php и объявить там необходимые вещи. Например в этом файле мы можем подключить библиотеки необходимые для тестирования, изменить глобальные и супер-переменные, объявить константы и т.д.

Пример bootstrap.php файла:

define( 'PLUGIN_PATH', __DIR__ '/../' );

require_once PLUGIN_PATH . '/vendor/autoload.php';

Конфигурационный XML-файл

Мы можем настроить выполнение команд с помощью конфигурационного XML-файла, в котором можно изменить стандартные настройки phpunit, указать папку с тестами, путь к бутстап файлу, настроить фильтры и другое.

Пример XML-файла:

<phpunit bootstrap="./bootstrap.php" colors="true">
    <testsuites>
        <testsuite name="Config-example-for-tests">
            <directory suffix=".php">./tests/</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist>
            <directory suffix=".php">../../</directory>
            <exclude>
                <directory>../../src</directory>
                <directory>../../tests</directory>
                <directory>../../vendor</directory>
            </exclude>
        </whitelist>
    </filter>
</phpunit>

Атрибутами или вложенными тегами к <phpunit> могут быть любые команды, которые есть в phpunit. В нашем случае

vendor/bin/phpunit /tests/ --bootsrap /tests/bootsrap.php --colors --test-suffix=".php" ...

Меняется на:

vendor/bin/phpunit --configuration phpunit.xml

Покрытие тестами (Tests coverage)

С помощью этого инструмента очень легко понять насколько качественно написаны тесты, сколько файлов покрыты тестами и какие строки в них покрыты. Данный инструмент очень сильно помогает отслеживать качество ваших тестов.

Необходимо к вашему php-cli подключить xdebug иначе coverage будет недоступен и вы получите уведомление об отсутствии модуля для тестирования.

Чтобы его использовать достаточно дописать к phpunit —coverage-{type} атрибут. Например очень удобно использовать HTML-формат покрытия:

vendor/bin/phpunit --configuration phpunit.xml --coverage-html coverage

После этого в проекте создается папка coverage и мы можем открыть файл index.html и просмотреть подробную информацию о каждом файле тестирования.

Источник: WP Punk.

Автор: Кобзарёв Михаил

Русский разработчик с 20-ти летним стажем. Работаю с PHP, ООП, JavaScript, Git, WordPress, Битрикс, Joomla, Drupal, Opencart, DLE, Laravel, Moonshine, SuiteCRM.

Оптимизирую сайты под Google Page Speed, настраиваю импорты для больших магазинов на WooCommerce + WP All Import. Пишу плагины на заказ. Все мои услуги.

Веду блог о разработке, дайджест в телеграмме и в ВК.

Вы всегда можете нанять меня.

Комментарии
Подписаться
Уведомить о
guest

2 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии
Sabir Ali
Sabir Ali
8 месяцев назад

В Вашем коде есть небольшая ошибка: вы не указали, какой именно метод Headdressожидается вызвать. Для этого необходимо использовать метод method из PHPUnit. Вот исправленный вариант:

use PHPUnit\Framework\TestCase;

class MockTest extends TestCase {
 public function testMock() {
   $headdress = $this->createMock(Headdress::class);
   $headdress
     ->expects($this->once()) // Ожидаем, что метод вызовется один раз
     ->method('back');       // Указываем, какой метод должен быть вызван
   $duck = new Duck($headdress);

   $this->assertTrue($duck->run());
 }
}
Предыдущая запись

Давайте дружить
в Telegram

Авторский блог вашего покорного слуги в Telegram про web, программирование, алгоритмы, инструменты разработчика, WordPress, Joomla, Opencart, Laravel, Moonshine, фильмы и сериалы