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

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

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 имеет много полезного функционала для тестирования.

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

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:

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

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

Обший отчет о покрытии по директориям и файлам

Отчет о покрытии методов класса

Подсветка покрытого и непокрытого кода

В телеграм канале DevOps от первого лица можно оставить комментарий или почитать интересные истории из практики DevOps