Часть 9: Codeception. Настройка. Unit-тесты (Тестирование ПО)
Оглавление
Продолжаем цикл статей Тестирование ПО. В этой части рассмотрим установку и настройку фреймворка codeception. Попутно перенесем все ранее написанные тесты на новую платформу.
Прежде, чем двигаться дальше нам необходимо познакомиться с таким фреймворком как codeception. На базе PHPUnit можно делать лишь блочные и модульные тесты. Его предназначение полностью расшифровывается в названии. Поскольку изначально он был заточен под применение исключительно для юнит-тестирования (и модульного конечно же).
PHPUnit никуда не делся, но так как найти абсолютно все ошибки лишь модульными тестами нельзя, то со временем стали появляться различные инструменты для проведения функционального или приемочного тестирования - одни из них это selenium, который открывает браузер при проверяет соответствие сгенерированной страницы том, что ожидает пользователь и имеет возможность заполнять формы и отправлять запросы.
Появилась потребность в инструментах более высокого уровня, которые могли бы позволить манипулировать всем этим разнообразием консольных команд, приложений и всем тем, что позволяет искать ошибки в приложениях. Одним из таких инструментов стал Codeception - надстройка над PHPUnit, которая помимо прочего умеет выполнять тесты в браузере, осуществлять сетевые запросы, заполнять формы и много чего еще.
Ранее выделялось четыре стадии тестирования проекта:
- юнит-тестирование
- интеграционное - оно же блочное
- функциональное
- приемочное
Было отмечено, что в рамках yii осуществлять чисто блочное тестирование не всегда возможно из-за его архитектурных особенностей.
Codeception несколько упрощает жизнь разработчика. В рамках этого инструмента существует три уровня изоляции тестов.
- Юнит-тесты. Этот уровень изоляции сочетает в себе как стандартные юнит-тесты, так и блочные.
- Функциональные тесты - это по-прежнему отправка данных в формы, проверка ответа сервера, тестирование внешнего api.
- Приемочные тесты - запускается headless-браузер (окна не открывается, браузер работает в фоновом режиме) и фреймворк выполняет действия от лица пользователя: двигает мышкой, вводит текст в поля ввода, разгадывает капчу, переходит по пунктам меню. Эта часть тестов способна находить ошибки в коде браузерных приложений.
установка и инициализация
Нам потребуется пакет codeception/codeception - основная составляющая фреймворка.
composer require --dev "codeception/codeception:^2.0.0"
Конструкция означает, что мы ставим запрашиваем самую последнюю версию из ветки 2.x. Выполняем данную команду на виртуальной машине в папке /var/www.
Дальше стоит поставить codeception/verify - достаточно удобный пакет, который позволяет писать предикаты, близкие к естественному языку. Это лишь синтаксический сахар. Сравните два теста, которые описаны ниже - первый с применением стандартных предикатов, а второй - с синтаксическим сахаром.
$this->assertEquals($user->getName(), 'test');
verify($user->getName())->equals('test');
Ставится достаточно просто.
composer require --dev "codeception/verify:^0.3.0"
Последнее, что нам может потребоваться - это модуль codeception/specify. Еще одна порция синтаксического сахара. Чуть позже мы будем применять ее на практике.
composer require --dev "codeception/specify:^0.4.0"
Не забываем указывать опцию –dev - она говорит о том, что при развертывании нашего проекта в релизе пакеты для выполнения тестов или любой другой девелоперской рутины не будет установлены.
Другой простой способ установить описанные выше компоненты - это добавить в секци require-dev несколько пакетов и выполнить провизию на машине.
"require-dev": {
"codeception/codeception": "^2.0.0"
"codeception/verify": "^0.3.0",
"codeception/specify": "^0.4.0"
}
Интеграция Codeception и Yii2
Прежде чем мы приступим к освоению нового инструмента переименуем папку tests - поскольку это название закреплено за тестами codeception, а наши более ранние эксперименты предназначались для того, чтобы понять, как работают тесты изнутри.
Чтобы все прошло удачно необходимо, чтобы папка tests и файл codeception.yml в корне проекта отсутствовали. Иначе инициализация скажет, что инфраструктура уже развернута. Проверяем, что у нас нет ничего лишнего и запускаем из консоли команду на подготовку окружения.
vendor/bin/codecept bootstrap --ns common\\tests common
Каталог с тестами общими для всего проекта будет создан в папке common. Да, мы не будем дальше использовать вызовый через composer run из-за бага, который был описан ранее. Опция –ns для нас жизненно необходима для того, чтобы все генерируемые фреймворком в процессе работы файлы попадали в правильный неймспейс сразу (это файлы из каталога tests/_generated).
Так же в каталоге common будет создан файл codeception.yml - это конфигурация для запуска.
Изначально codeception не умеет дружить с yii2 полноценно. Поэтому нам требудется его этому научить. Один из параметров файла настроект указывает на _bootstrap.php - скрипт, который исполняется перед запуском тестов. Вот тут-то мы и выполним загрузку базовой части фреймворка в память.
Теперь добавим нужные для дальнейшей работы параметры в codeception.yml.
actor: Tester
paths:
tests: tests
log: tests/_output
data: tests/_data
support: tests/_support
envs: tests/_envs
settings:
bootstrap: _bootstrap.php
colors: true
memory_limit: 1024M
extensions:
enabled:
- Codeception\Extension\RunFailed
modules:
config:
Yii2:
configFile: 'config/test-local.php'
Здесь мы указываем, что модуль config будет использовать специфический настройки из yii2 для инициализации фреймворка.
Переименуем файл acceptance.suite.yml в acceptance.suite.yml.example. Делаем мы это для того чтобы codeception не пытался запускать приемочные тесты раньше времени. Поскольку для низ нужна дополниельная конфигурация (мы помним из теоретичесеой части что приемочные тесты - это тесты выполняемые в браузере от лица пользователя или в нашем сылучае от лица автоматизированной системы запуска вроде selenium). К приемочным тестам мы еще вернемся в одной из следующих частей.
Поправим настройки приемочныйх и функциональных тестов.
unit.suite.yml
class_name: UnitTester
modules:
enabled:
- Asserts
- Yii2:
part: [orm]
functional.suite.yml
class_name: FunctionalTester
modules:
enabled:
- Yii2
Тем самым при инициализации тестового окружения будет инициализирован и сам фреймворк. А еще обязательно нужно заигнорить в гите папку common/tests/_support/_generated положив в каталог _support файл .gitignore со следующим содержимым:
_generated
Перенос тестов на новую платформу
Теперь все готово к том, чтобы попробовать, как старые тесты работают на новой платформе. В случае UserTest нет ничего проще - просто копируем файл в новый каталог, изменяем порядок наследования. Теперь нужно наследовать классы тестов от Codeception\Test\Unit.
Если мы оставим все как есть, то увидим много-много ошибок в консоли (для каждого теста). Попробуем это сделать.
$ cd common
$ ../vendor/bin/codecept run
Почему так происходит? Ранее мы указыали путь к тестовой конфигурации проекта в файлах настройки окружения. Так вот. TestCase из PHPUnit ничего про это окружение не знает, а следовательно он ничего не знает про инициализацию yii. Из этого следует, что объект \Yii::$app нам недоступен и мы не можем извлекать из него зависимости.
---------
7) UserTest: Password validation
Test tests/unit/model/UserTest.php:testPasswordValidation
[PHPUnit_Framework_Exception] Trying to get property of non-object
#1 /var/www/common/tests/unit/model/UserTest.php:77
Как быть? Отнаследоваться от \Codeception\Test\Unit. Этот класс уже знает, что нужно делать для запуска движка перед тестами. Если мы заглянем в исходники этого класса, то найдем там код, который и отвечает за инициализацию модулей (настройки берутся из массива part для каждого типа кейсов).
namespace common\tests\unit\model;
use Codeception\Test\Unit;
use common\models\User;
class UserTest extends Unit
{
//....
}
Повторный запуск и все тесты завершаются корректно.
Перенос тестов DBUnit
У нас есть LoginFormTest, который спользует функционал DBUnit для добавления сущностей в базу данных. Как с ним быть? Все довольно просто - у yii есть механизм acrivefixture, который подмешивает в базу нужные данные и представляет из себя как раз таки иную реализацию того, что представляет из себя DBUnit.
Наиболее распространенная задача расширения для работы с базой из состава PHPUnit - загрузить данные (не будем рассматривать задачу валидации схем - редко кто из пользователей готовых фреймворков ею пользуется). Чтобы это сделать нам потребуется несколько простых вещей:
- Проинициализироать класс фикстуры для нужной модели. В данном случае это common\fixtures\User ```php namespace common\fixtures;
use yii\test\ActiveFixture;
class User extends ActiveFixture
{
public $modelClass = ‘common\models\User’;
}
- Создать хранилище, в котором будут содержаться данные фейковых пользователей ( **common/tests/_data/User.php** )
```php
return [
[
'username' => 'test',
'email' => 'test@test.test',
'auth_key' => 'ssssssssssssssssssssssssssssssss',
'password_hash' => '$2y$13$PP1EDCr7ujdhTxZT2DV96uM8e2rcdXHY1xAQINCIiB0gOck/VBwN6',
],
[
'username' => 'test1',
'email' => 'test1@test.test',
'auth_key' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
'password_hash' => '$2y$13$PP1EDCr7ujdhTxZT2DV96uM8e2rcdXHY1xAQINCIiB0gOck/VBwN6',
]
];
- Изменить порядок наследования класса LoginFormTest и отнаследовать его от Codeception/Test/Unit
- Убрать все методы, которые были реализгованы для DBUnit
- Реализовать метод _before, который содержит подгрузку фикстуры ```php /**
- Use user fixtures for database testing
*/
public function _before()
{
$this->tester->haveFixtures([
‘user’ => [
‘class’ => UserFixture::className(),
‘dataFile’ => codecept_data_dir() . ‘user.php’
]
]);
} ``` - Доавить поддержку fixtures в настройки unit.suite.yml
part: [orm, fixtures]
- Помните мы добавляли сохранение модулей фреймворка которые мы оборачивали в mock’и? Этот функционал нужно убрать - настройки codeception по умолчанию инициализируют окружение yii законово ля каждого теста.
Все работает.
../vendor/bin/codecept run
Разделения тестов на фронтенд, бекенд и core-составляющие
Безусловно, можно сваливать все тесты в одну кучу (ядро, бек, фронт) и запускать их одной командой. Так, например сделано в yii2-standard-template. Но когда проект у нас сложный, то время выполнения тестов непрерывно растет и надо как-то разделять их на блоки, а для этого у нас есть прекрасная возможность - сама архитектура приложения этому способствует поскольку поделена на три части.
Для того, чтобы было удобно работать с тестами существует возможность глобального конфигурирования codeception - тесты каждой составляющей в своей папке, а глобальная конфигурация уровнем выше.
В нашем случае это файл codeception.yml в корне проекта.
# global codeception file to run tests from all apps
include:
- common
- frontend
- backend
paths:
log: console/runtime/logs
settings:
colors: true
Разработчики шаблона о нас позаботились и заранее все сделали до нас. Мы лишь повторили их путь. И теперь можно запускать тесты уже и корня проекта. При таком способе будут выполнены все юнит и функиональные тесты для всех блоков системы.
Зачем все это?
После выполнения всех действия возникает вопрос: разработчики yii2-advanced-template уже все сделали до нас (положили нужные файлы в ынужные места, настроили окружение), зачем нам повторять этот путь? Конечно для того, чтобы знать как устроены подобные вещи изнутри. В следующих частях мы уже перейдем непосредственно к самим тестам, а сейчас мы только учимся, осваиваем технологию.
Домашнее задание
Самая главная проблема нашего кейса: мы тестировали функционал фронтенда внутри тестов, которые посвящены ядру системы (я про LoginForm). Целесообразно вынести эти тесты во frontend-составляющую, что вы и сделаете в качестве домашенй работы.
Помимо всего авторы yii постарались и покрыли тестами весь фунционал шаблона проекта. Мы же в вами функциочнал исправляем под себя и при это не испчравляем уже имеющиеся тесты. И они, очевидно, падают. В качестве домашенего задания поправьте тесты фронт и бек-составляющих так, чтобы они отвечали текущей схеме базы.
Одна из проблем с которой вы столкнетесь - это валидация password_reset_token. Суть в том, что срок жизни токена закодирован в самом токене. И на момент описания статьи этот токен уже истек. Подумайте как обойти эту проблему не использую рандомизированные данные. Если вы не сможете ничего придумать, то посмотрите в исходники к статье. Рекомендую посмотреть на метод User::isPasswordResetTokenValid().
В этой части мы говорим исключительно о юнит тестах. Поэтому не стоит сразу браться за исправление функциональных тестов - о них дальше. Да, безусловно, на реальном проекте оставлять поломанный код в системе контроля версий нельзя. Помните об этом! Мы это делаем только чтобы не сильно перегружать информацией каждую часть этого цикла!
Исходные тексты
Ссылки и документация
- Codeception for yiiframework
- YiiFramework: Модульное тестирование
- Yii::$app null when trying to access ActiveRecord functionality from a Unit test
- Unit testing error on database insert yii2 framework
- Сервис на Yii2: Тестирование приложения с Codeception
- Yii::$app isn’t being initialized during unit tests. Yii2, Codeception 2.2.4
- Fixtures - Testing - The Definitive Guide to Yii 2.0 - Yii Framework
Категории: Разработка HowTo