Модульное тестирование (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.
В Вашем коде есть небольшая ошибка: вы не указали, какой именно метод
Headdress
ожидается вызвать. Для этого необходимо использовать методmethod
из PHPUnit. Вот исправленный вариант:Спасибо, поправил!