PHPUnit быстрый старт

16.12.2021

PHPUnit - это фреймворк для тестирования кода (модульные, интеграционные, системные тесты) на PHP. Документация на русском языке достаточная, но на английском все же полная.

К слову документация исчерпывающая, необходимость что-то искать в интернета отпадает. В данной статье я затрону, моменты которые могут тормозить быстрый старт, и очень кратко расскажу про некоторые необходимости, все остальное можно найти в документации.

Прочитав книгу Роберта Мартина Идеальный программист. Как стать профессионалом разработки ПО, я решил погрузиться в тестирование кода. Чему очень рад :)

Подготовка

Установка через composer проста (но есть и другие варианты установки) - в корне директории с php скриптами необходимо:

•••
bash
composer require --dev phpunit/phpunit ^latest

Если используется автозагрузка классов (PSR-4), тогда в composer.json необходимо добавить ключ autoload для указания корневого пространства имен, откуда будут грузится классы:

•••
json
"autoload": { "psr-4": { "MyNamespace\\": "src" } }

А если по ходу дела поменялось пространство имен (после того как проект был инициализован), например с MyNamespace на My\\Space, то необходимо изменить его в composer.json и:

•••
bash
composer update

Если этого не сделать, используя свой идентичный PSR-4 автозагрузчик, тогда при проведении тестов, обработчик автозагрузки классов будет запускаться и PHPUnit будет ругаться на использование непокрывыемого тестами кода, например так::

•••
bash
17) ConverterTest::testItemsToArray This test executed code that is not listed as code to be covered or used: - /src/init.php:10 - /src/init.php:11 - /src/init.php:13 - /src/init.php:6 - /src/init.php:7

Написание тестов

Написание тестов подробно расписано в этом разделе документации. Там же (в документации) можно прочитать про фикстуры и про тестовых двойников, которые нужны для имитации некоторых объектов кода, а сама имитация нужна для изоляции тестируемого кода.

Тестирующий код следует распологать в отдельной от основго кода директории, например в tests. В зависимости от переданных параметров PHPUnit может запускать все файлы в директории или конкретный файл.

Например эта команда запустит все файлы в директории tests/Unit/ (и во вложенных директорях) и перед тестами запустит файл tests/Unit/bootstrap.php:

•••
bash
vendor/bin/phpunit --bootstrap tests/Unit/bootstrap.php tests/Unit/

В каждом файле тестов:

  • должен располагаться класс с именем файла наследуемый от PHPUnit\Framework\TestCase
  • методы класса должны начинаться с test и иметь внутри себя утверждения
  • класс и методы должны иметь аннотации

Пример

Ниже представлен лишь малый функционал по написанию тестов, на самом деле PHPUnit имеет много полезного функционала для тестирования.

Предположим у нас есть класс:

•••
php
class Receipt { const TYPE_COMING = 0; const TYPE_REFUND = 1; public function __construct() { $this->type = self::COMING; } /** * @throws InvalidArgumentException */ public function setType(int $type): self { if($type != self::COMING && $type != self::REFUND) throw new InvalidArgumentException(); $this->type = $type; return $this; } public function getType(): int { return $this->type; } private $type; }

Это упрощенный пример класса из одного рабочего проекта. Класс Receipt содержит одно поле type и 2 метода для установки и получения этого поля. При создании объекта этому полю присваивается дефолтное значение. Метод установки значения setType может выбрасывать исключения при невалидном значении.

Вопросы на которые должен ответить тест:

  • можно ли в любой момент времени утверждать что класс устанавливает значение по умолчанию, которое мы ожидаем?
  • можно ли утверждать что при использовании метода setType будет установлено валидное значение?

Теперь напишем тест для этого класса:

•••
php
use PHPUnit\Framework\TestCase; use Receipt; /** * @uses Receipt */ class ReceiptTest extends TestCase { /** * @covers Receipt::__construct * @covers Receipt::setType * @covers Receipt::getType */ public function testSetGetType() { $receipt = new Receipt(); // проверяем дефолтное значение $this->assertSame(Receipt::TYPE_COMING, $receipt->getType()); // проверка установки валидного значения $this->assertSame($receipt, $receipt->setType(Receipt::TYPE_REFUND)); $this->assertSame(Receipt::TYPE_REFUND, $receipt->getType()); // проверка выброса исключения в случае передачи невалидного значения $this->expectException(InvalidArgumentException::class); $receipt->setType(-1); } }

При наличии тестов, в любой момент времени (при разработке, перед коммитом и прочее) можно утверждать что код работает так как ожидается.

Запуск тестов

Про опции командной строки детально написано в документации.

Тесты можно запускать через composer, для этого в composer.json необходимо объявить команды в scripts, например так:

•••
json
"scripts": { "test-unit": "vendor/bin/phpunit --colors=always --coverage-html coverage-report-html --bootstrap tests/Unit/bootstrap.php tests/Unit/", "test-integration": "vendor/bin/phpunit --colors=always --bootstrap tests/Integration/bootstrap.php tests/Integration/", "test-system": "vendor/bin/phpunit --colors=always --bootstrap tests/System/bootstrap.php tests/System/" }

В этом примере объявлено 3 команды на запуск модульных, интеграционных и системных тестов, при этом для каждого теста свои настройки.

Запускать тесты через composer можно так:

•••
bash
composer run-script test-unit

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

•••
bash
Warning: No filter is configured, code coverage will not be processed

Решается это генерацией конфига phpunit.xml при помощи PHPUnit:

•••
bash
phpunit --generate-configuration

Результаты покрытия тестов

Все кроме покрытия просто, а здесь я немного задержался :)

Среда организована, тесты написаны, теперь хочется в красивом виде узнать о покрытии кода тестами.

Для начала необходимо установить xdebug библиотеку для нужной версии php:

•••
bash
apt install php7.3-xdebug

В самом простом варианте информацию о покрытии можно увидеть в выводе командной строки после завершения тестов при помощи опции --coverage-text:

Информация о покрытии тестами при использовании --coverage-text
Информация о покрытии тестами при использовании --coverage-text

Но есть еще html вариант, где значительно больше информации о покрытии в более наглядном виде. Для генерации надо использовать опцию --coverage-html с указанием директории вывода отчета (пример выше в composer.json).

Обший отчет о покрытии по директориям и файлам
Обший отчет о покрытии по директориям и файлам
Отчет о покрытии методов класса
Отчет о покрытии методов класса
Подсветка покрытого и непокрытого кода
Подсветка покрытого и непокрытого кода