
Для модульного тестирования, как и для других типов, в фреймворке Codeception существует два способа написания тестов, которым соответствуют два типа наименования файлов: Cept и Cest. Cept строится по сценарию (функциональное программирование), а Cest на базе объектов (ООП). Кроме того, Codeception может выполнять оригинальные PHPUnit-тесты для модульного тестирования т.к. наследуется от данной библиотеки.
Создание по типу cept:
codecept generate:cept unit Nameгде «Name» - названия файла теста.
Появится файл tests\unit\NameCept.php с содержимым:
<?php $I = new UnitTester($scenario); $I->wantTo('perform actions and see result');
Создание по типу cest:
codecept generate:cest unit NameПоявится файл tests\unit\OneCest.php с содержимым:
<?php class OneCest { public function _before(UnitTester $I) { } public function _after(UnitTester $I) { } // tests public function tryToTest(UnitTester $I) { } }Каждый публичный метод, не начинающийся с нижнего подчеркивания, будет выполнен как тест.
Это обычные для Codeception типы организации тестов.
Для использования стандартных PHPUnit утверждений (asserts), должен быть включен модуль «Asserts» в конфигурационном файле tests\unit.suite.yml (по-умолчанию уже включен).
Доступ к утверждениям (asserts) можно получить с помощью переменной $I:
$I->assertTrue(true);
Кроме того есть возможность последним аргументом указывать текстовые сообщения, которые, во-первых, делают более читаемыми теста, а во-вторых отобразятся в консоли в случае провала указанного теста:
$I->assertTrue(true, 'message');
Тип cest может использовать события _before и _after непосредственно в тестирующем классе. Эти и другие события, для обоих типов тестов могут быть использованы в помощнике tests\_support\Helper\Unit.php
Список событий можно посмотреть по ссылке.
Правда нет полного аналога методу setUpBeforeClass(), код которого выполнялся бы перед выполнением тестов из каждого отдельного класса. Можно использовать _beforeSuite(), код которого выполнится только один раз до начала тестирования.
Создание тестов по типу phpUnit:
codecept generate:test unit Name
Появится файл tests\unit\NameTest.php с содержимым:
<?php class TwoTest extends \Codeception\Test\Unit { /** * @var \UnitTester */ protected $tester; protected function _before() { } protected function _after() { } // tests public function testSomeFeature() { } }
В данном случае, создается класс, который по цепочке наследования наследуется от phpUnit и поэтому тесты можно писать в стиле этого фреймворка и используя его методы. При необходимости, можно перенести ваши готовые тесты из phpUnit целиком. Только если вы использовали доп.пакет phpunit/dbunit для работы с базой данных в тестах phpUnit, не забываем подключить данный пакет в классе теста. Для для этого достаточно подключить трейт:
use PHPUnit_Extensions_Database_TestCase_Trait;или
use PHPUnit\DbUnit\TestCaseTrait;в зависимости от версии phpUnit. И папку _data с xml файлом для заполнения данными тестовой таблицы нужно скопировать в папку tests\unit.
Методы _before() и _after() вызывают методы setUp() и tearDown() у phpUnit, поэтому в них так же можно писать код который выполняется перед каждым методом в тестовом классе и после каждого метода соответственно.
При данном способе написания модульных тестов можно использовать все методы phpUnit, например setUpBeforeClass().
Так же как и для других типов тестов есть возможность подключать и использовать модули Codeception. Таким образом можно использовать тот же метод seeInDatabase() модуля Db для проверки наличия данных в БД (подробнее тут).
В своей статье "Основы PHPUnit - 1 часть." и я писал как создаются модульные тесты. Codeception выступает в качестве обертки для PHPUnit. Поэтому подробнее ознакомиться с модульным тестированием и посмотреть примеры можно по ссылке.
Работа с имитирующими объектами в Codeception.
Для удобства использования, Codeception имеет свою обертку поверх стандартной работы с имитирующими объектами в phpUnit. Использовать ее можно во всех типах модульных тестов Codeception. А почитать про имитирующие объекты в phpUnit можно тут.
Подключаем класс Stub в нужном файле теста:
use Codeception\Util\Stub;
Методы создающие имитирующий объект без выполнения метода конструктора класса.
- Метод make() позволяет переопределить нужное свойство или метод. Остальные свойства и методы остаются без изменений. Не забываем, что метод конструктора не будет выполнен, а значит некоторые свойства объекта могут быть не инициализированы, если это предусмотрено в конструкторе.
Переопределяем свойство name класса User:
$user = Stub::make(\app\User::class, ['name' => null]);
Переопределяем метод readAll() класса User:
$user = Stub::make(\app\User::class, ['readAll' =>function(){ return true; }]);
- Метод makeEmpty():
- конструктор не запускается.
- свойства и методы могут быть установлены как второй параметр.
- можно установить даже защищенные и частные свойства.
// создаем пустой класс с указанными свойствами $user = Stub::makeEmpty(\app\User::class, array('name' => 'Serj'));
- Метод makeEmptyExcept():
- конструктор не запускается.
- свойства и методы могут быть установлены как третий параметр.
- можно установить даже защищенные и частные свойства.
$user = Stub::makeEmptyExcept(\app\User::class, 'read', array('name' => 'Serj'));т.е. в данном примере метод read будет выполнять код указанный в классе User, тогда как другие методы будут возвращать null.
Методы создающие имитирующий объект с выполнением метода конструктора класса.
Есть несколько схожих методов, которые создают имитирующий объект с запуском конструктора класса: Stub::construct, Stub::constructEmpty, Stub::constructEmptyExcep в остальном они работают аналогично методам типа “make”.
- Метод construct() позволяет переопределить указанные свойства и методы.
- Метод constructEmpty() создает объект класса, все методы которого возвращают null и переопределить указанные свойства/методы.
- Метод constructEmptyExcep() создает объект класса, все методы которого возвращают null кроме одного указанного метода, который будет выполнять свой оригинальный код. так же есть возможность переопределить нужные свойства/методы.
Их использование позволяет, выполнив код конструктора, инициализировать указанные в нем свойства, и при этом, в зависимости от выбора одного из этих методов, переопределить нужные свойства/методы или расставить заглушки (значения null).
Например создадим имитирующий объект на базе класса User аналогичный оригиналу но с переопределенным методом readAll():
$db = new \app\Db(); $user['name'] = 'Test'; $user['email'] = 'test@gmail.com'; $user = Stub::construct(\app\User::class, array($db, $user), array('readAll' => function(){ return ['name' => 'Sveta', 'email' => 'sveta@mail.ru']; }));
Вместо названия класса, в качестве первого аргумента данным методам можно указывать объект данного класса.
- Метод Stub::update позволяет переопределить свойства/методы у созданного ранее имитирующего объекта.
$db = new \app\Db(); $user['name'] = 'Evgen'; $user['email'] = 'evgen@gmail.com'; $user = Stub::constructEmpty(\app\User::class, array($db, $user)); Stub::update($user, [ 'name' => 'newName', 'email' => 'newNamegmail.com' ]);Это позволяет «по-быстрому» внести изменения в существующий имитирующий объект. Так же можно добавить новое свойство которое не указано в оригинальным классе/объекте.
Еще пример – допустим создали пустой имитирующий объект у которого все методы являются заглушками (возвращают null):
$user = Stub::makeEmpty(\app\User::class);а далее, для какой-то проверки понадобилось переопределить метод readAll():
Stub::update($user, [ 'readAll' => function(){ return true; } ]);
Так же есть методы never, once, exactly и др. для уточнения того сколько раз должен вызываться указанный метод.
Например укажем что метод getName() не должен быть вызван ни разу в процессе тестирования иначе выбросить исключение:
$user = Stub::make('User', array('getName' => Stub::never(), 'someMethod' => function() {}));
Подробное описание этих и других методов с примерами можно посмотреть
по ссылке или в файле vendor\codeception\codeception\src\Codeception\Util\Stub.php
Синтаксический сахар Codeception.
Синтаксический сахар (англ. syntactic sugar) в языке программирования — это синтаксические возможности, применение которых не влияет на поведение программы, но делает использование языка более удобным для человека. Для этих целей предлагается установка двух пакетов:
- codeception/verify;
- codeception/specify.
Данные пакеты могут использоваться при написании модульных и интеграционных тестов в Codeception для удобства и расширения основного функционала PHPUnit.
codeception/verify
Глобальная установка (в каталог текущего пользователя):composer global require --dev "codeception/verify"Для установки в текущий проект:
composer require --dev "codeception/verify"
После установки сразу можно использовать данный синтаксис, как-то подключать не нужно.
Сравните два теста, которые описаны ниже - первый с применением стандартного синтаксиса PHPUnit, а второй — с использованием пакета verify.
$this->assertEquals($user->getName(), 'test');и
verify($user->getName())->equals('test');
Можно сначала указывать комментарий, который будет так же выведен при ошибке:
verify('сравниваем имя пользователя', $user->getName())->equals('test');
Вместо функции verify() можно использовать синоним:
expect()с такими же аргументами.
Методы утверждений (asserts) используемые данным пакетом схожи с теми, что использует PHPUnit. В примере вместо assertEquals() используется equals(). Чаще всего достаточно просто убрать приставку «assert»:
assertTrue – true;
assertFalse – false;
assertNotNull – notNull;
но есть и такие:
assertEmpty - isEmpty
Посмотреть соответствие методов можно в файле vendor\codeception\verify\src\Codeception\Verify.php.
Еще примеры.
codeception/specify
Пакет позволяет писать несколько тестов PHPUnit в одном методе разбивая их на блоки. Код внутри specify блоков изолирован. Любое изменение свойств объектов и переменных не будет отражено в других блоках кода.Так же можно указать комментарий к каждому блоку теста.
Установка:
composer global require --dev "codeception/specify"для установки каталог vendor текущего проекта:
composer require --dev "codeception/specify"
После установки нужно подключить трейт в классе теста:
class UserTest extends \Codeception\Test\Unit { use \Codeception\Specify; // подключаем пакет Specify ...
Пример.
public function testReadAll() { $this->specify('при наличии данных в БД массив не пуст', function (){ $this->assertNotEmpty($this->user->readAll()); }); $this->specify('при пустой таблице возвращает пустой массив', function (){ //кол-во элементов в массиве данных должно быть = 0 $this->assertCount(0, $this->user->readAll()); }); }
При этом я заметил конфликт данного пакета с расширением для PHPUnit используемом для работы с БД - DBUnit.
В данном случае возникает ошибка:
[PHPUnit_Framework_Exception] ReflectionProperty::setValue(): Cannot assign to an array of nodes (duplicate subnodes or attr detected)
Данная статья является одной из серии статей про фреймворк для тестирования Codeception. Читайте так же:
- Установка, настройка и базовое использование фреймворка для тестирования "Codeception".
- Codeception - взаимодействие с базой данных. Модуль Db, фикстуры.
- Codeception - приемочные тесты. Использование Selenium.