PHPUnit - это фреймворк для тестирования кода (модульные, интеграционные, системные тесты) на PHP. Документация на русском языке достаточная, но на английском все же полная.
К слову документация исчерпывающая, необходимость что-то искать в интернета отпадает. В данной статье я затрону, моменты которые могут тормозить быстрый старт, и очень кратко расскажу про некоторые необходимости, все остальное можно найти в документации.
Прочитав книгу Роберта Мартина Идеальный программист. Как стать профессионалом разработки ПО, я решил погрузиться в тестирование кода. Чему очень рад :)
Установка через composer проста (но есть и другие варианты установки - в корне директории с php скриптами необходимо:
composer require --dev phpunit/phpunit ^latest
Если используется автозагрузка классов PSR-4, тогда в composer.json
необходимо добавить ключ autoload
для указания корневого пространства имен, откуда будут грузится классы:
"autoload": {
"psr-4": {
"MyNamespace\\": "src"
}
}
А если по ходу дела поменялось пространство имен (после того как проект был инициализован), например с MyNamespace
на My\\Space
, то необходимо изменить его в composer.json
и:
composer update
Если этого не сделать, используя свой идентичный PSR-4 автозагрузчик, тогда при проведении тестов, обработчик автозагрузки классов будет запускаться и PHPUnit будет ругаться на использование непокрывыемого тестами кода, например так:
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
:
vendor/bin/phpunit --bootstrap tests/Unit/bootstrap.php tests/Unit/
В каждом файле тестов:
PHPUnit\Framework\TestCase
test
и иметь внутри себя утверждения
Ниже представлен лишь малый функционал по написанию тестов, на самом деле PHPUnit имеет много полезного функционала для тестирования.
Предположим у нас есть класс:
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
будет установлено валидное значение?
Теперь напишем тест для этого класса:
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
, например так:
"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 можно так:
composer run-script test-unit
PHPUnit использует конфигурационный файл для общих настроек тестов, однако на старте его нет, и если запускать тесты без него то можно получить:
Warning: No filter is configured, code coverage will not be processed
Решается это генерацией конфига phpunit.xml
при помощи PHPUnit:
phpunit --generate-configuration
Все кроме покрытия просто, а здесь я немного задержался :)
Среда организована, тесты написаны, теперь хочется в красивом виде узнать о покрытии кода тестами.
Для начала необходимо установить xdebug библиотеку для нужной версии php:
apt install php7.3-xdebug
В самом простом варианте информацию о покрытии можно увидеть в выводе командной строки после завершения тестов при помощи опции --coverage-text
:
Но есть еще html вариант, где значительно больше информации о покрытии в более наглядном виде. Для генерации надо использовать опцию --coverage-html
с указанием директории вывода отчета (пример выше в composer.json
).