Установка и основы работы с Codeception были рассмотрены в первой статье данной серии. Сейчас остановимся на модульном(unit) тестировании.

Для модульного тестирования, как и для других типов, в фреймворке 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():
- создает класс, все методы которого возвращают null.
- конструктор не запускается.
- свойства и методы могут быть установлены как второй параметр.
- можно установить даже защищенные и частные свойства.
// создаем пустой класс с указанными свойствами
$user = Stub::makeEmpty(\app\User::class, array('name' => 'Serj'));

  • Метод makeEmptyExcept():
- создает класс, все методы которого возвращают null, кроме одного указанного.
- конструктор не запускается.
- свойства и методы могут быть установлены как третий параметр.
- можно установить даже защищенные и частные свойства.
$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. Читайте так же: