Модульное тестирование (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( '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.