Целью данной статьи я ставил показать людям, не знакомым с тестированием, как можно действительно быстро начать тестировать, собрав все в одном месте с минимумом воды и на русском языке. Пусть это будет весьма примитивно. Пусть не очень интересно людям, которые уже живут по TDD, SOLID и другим принципам. Но дочитав до конца, любой желающий сможет сделать свой первый уверенный шаг в мир тестирования.


Мы рассмотрим приемочные (Acceptance), функциональные (Functional) и юнит-тесты или модульные тесты (Unit-Tests).



Также, на эту статью, подтолкнуло то, что много статей, с названием "Codeception", на самом деле — это просто 1 acceptace тест.
PS: Предупреждаю сразу, что я не профи и могу допускать ошибки во всем.

Установка Composer & Codeception


Если вас ставят в тупик фразы


$ composer require "codeception/codeception:*"
$ alias cept="./vendor/bin/codecept"

, то под катом я расскажу более подробно. Либо - идем дальше.

Для начала работы нам нужен замечательный инструмент Composer. В большинстве проектов он уже будет установлен. Но установить его — также не является проблемой. Все варианты перечислены на его официальной странице: https://getcomposer.org/download/ Наиболее банальным и простым способом является прокрутить вниз страницы и просто скачать *.phar файл в корень вашего проекта.


Лучшие практики вам обязательно скажут, что так делать — плохо. Необходимо разместить его в /etc/bin, дать права на выполнение и переименовать в composer. Прислушайтесь к ним, когда дочитаете статью до конца.


Второй шаг в настройке Composer, а также очень частый ответ, что делать, когда Композер сломался: обновить/установить FXP-плагин. Он "живет" по адресу: https://packagist.org/packages/fxp/composer-asset-plugin И устанавливается часто командой:
$ php composer.phar global require "fxp/composer-asset-plugin:~1.3.1"
Обратите внимание, что версию надо вписывать ту, которая будет отображена на сайте в момент прочтения этой статьи.


Финальная настройка перед началом работы — установить себе Codeception, с помощью Composer:
$ php composer.phar require "codeception/codeception:*"


После чего исполняемый файл Codeception доступен для нас в подкаталоге ./vendor/bin/codecept для Linux и ./vendor/bin/codecept.bat для Windows. Набирать это перед каждым запуском долго. Поэтому делаем сокращения:
Для Linux: $ alias cept="./vendor/bin/codecept"
Для Windows в корне проекта (откуда будем запускать тесты) создаем новый bat-файл: cept.bat


@echo off
@setlocal
set CODECEPT_PATH=vendor/bin/
"%CODECEPT_PATH%codecept.bat" %*
@endlocal

После чего команда cept, в консоли, должна вернуть help-страницу по Codeception. А если вам хочется запускать cept.bat из любого каталога — посмотрите в сторону директивы PATH.


И пару подсказок на эту тему:
Удаление пакета из композера: $ php composer.phar remove codeception/codeception
Если Вы столкнетесь с проблемой: "Fatal error: Allowed memory size of 12345678 bytes exhausted". Composer тут же подскажет ссылку, на которой будет написан немного модифицированный вызов: $ php -d memory_limit=-1 composer.phar {ваша_команда}


Создание первого теста: приемочный или Acceptance


Сейчас мы в шаге от своего первого теста. Проверим банально, что у нашего сайта открывается главная страница и страница About. Что они возвращают корректный код ответа "200" и содержат ключевые слова.


Собственно — это и есть суть приемочных тестов: проверить то, что доступно человеку, далекому от программирования: просмотр содержимого страницы, попытка залогиниться и т.д.


$ cept bootstrap — делаем разовую инициализацию, после первой установки
$ cept generate:cept acceptance SmokeTest — создаем первый тестовый сценарий


Открываем tests/acceptance/SmokeTestCept.php и дописываем к имеющимся двум строчкам new AcceptanceTester и wantТo свои. На выходе у нас должно получится:


$I = new AcceptanceTester($scenario);
$I->wantTo('Check that MainPage and About are work');
$I->amOnPage('/');
$I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
$I->see('Главная блога'); // ! Тут часть фразы с вашей главной
$I->amOnPage('/about');
$I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
$I->see('Обо мне'); // ! Тут часть фразы с вашей страницы about

Как Вы понимаете: запускать рано. Вы получите сообщение о том, что тест не пройден. Т.к. он не совсем в курсе, у какого сайта надо открыть главную страницу. Правим секцию modules.config.PhpBrowser.url в файле tests/acceptance.suite.yml. Например у меня там получилось: url: http://rh.dev/


Также в коде теста, сразу глаза бросается дубляж. Его можно отрефакторить, добавив новый метод в класс tests/_support/AcceptanceTester.php. Либо отнаследовавшись от него — создать себе собственный и добавить метод туда. Но это уже другой разговор не про тесты.


Итак, жмем следующие команды:


$ cept build — после внесения правок в файлы конфигурации всегда необходима пересборка
$ cept run acceptance — запускаем тест на выполнение


Вы должны получить сообщение: OK (1 test, 4 assertion)


Собственно — всё! Вы создали первый тест, который за Вас может проверить адекватность страниц по всему сайту. Что особенно полезно, когда этих страниц становится много, а шеф хочет с каждым разом накатывать все быстрее и быстрее. Не забывая раздавать нагоняи, что на сайте что-то сломалось.


В дальнейшем вы можете проверять также формы, аяксы, REST, JavaScript через Selenium и разные другие вещи.


Создание первого теста: функциональные или Functional


Сразу важная цитата из документации: "В случае, если Вы не используете фреймворки, практически нет смысла в написании функциональных тестов."
Для данного рода тестов Вам необходим любой фреймворк из поддерживаемых Codeception: Yii1/2, ZF, Symfony и т.д. Это касается только функциональных тестов.
К сожалению не смог найти конкретную ссылку со списком того, что поддерживается.

Функциональные тесты немного сходны с приемочными. Но в отличие от последних — не требуется запускать веб-сервер: мы вызываем наш framework, с эмуляцией переменных запроса (get, post).
Официальная документация рекомендует тестировать нестабильные части приложения с помощью функциональных тестов, а стабильные с помощью приемочных. Это обусловлено тем, что функциональные тесты не требуют использования веб сервера и порой, могут предложить более подробный отладочный вывод.


Первое, с чего нам надо начать: правим файл конфигурации в tests/functional.suite.yml, указав в нем модуль своего фреймворка, вместо фразы "# add a framework module here". А все тонкости настройки — придется прочесть в официальной документации.


Я покажу на примере не шаблонного Yii2 (если у вас установлен шаблон Basic или Advanced, то вверху этой страницы есть описание и такого варианта): http://codeception.com/for/yii#Manual-Setup--Configuration


Посмотреть шаги
  1. В файле tests/_bootstrap.php добавляем константу: defined('YII_ENV') or define('YII_ENV', 'test');. Если файла нет — создаем и добавляем в корневой codeception.yml settings.bootstrap:_bootstrap.php. Такие файлы необходимо будет вкладывать во все папки с тестами. Если забудете — Codeception вам об этом напомнит.
  2. В папке config yii-приложения, рядом с main.php кладем test.php, который заполняем из мануала
  3. Экспертным путем обнаружил, что модуль не видит мои vendors и испытывает проблемы с пониманием, где он находится в файловой системе. Поэтому дополнил test.php еще парой строк:
    require_once(__DIR__ . '/../../../vendor/autoload.php');
    require_once(__DIR__ . '/../../../vendor/yiisoft/yii2/Yii.php');
    require_once(__DIR__ . '/../../../common/config/bootstrap.php');
    // @approot - мой собственный алиас. + штатные еще не доступны в этом месте
    $_SERVER['SCRIPT_FILENAME'] = realpath(Yii::getAlias('@approot/web/index.php'));
    $_SERVER['SCRIPT_NAME'] = '/'.basename($_SERVER['SCRIPT_FILENAME']);

$ cept generate:cept functional myFirstFunctional — создаем первый тестовый сценарий
Созданный файл tests/functional/myFirstFunctionalCept.php заполняем сходно с прошлым файлом теста. С одним лишь отличием в первой строке:
$I = new FunctionalTester($scenario);


И дальше по пройденному выше материалу:


$ cept build — после внесения правок в файлы конфигурации всегда необходима пересборка
$ cept run functional — запускаем тест на выполнение


Вы должны получить сообщение: OK (1 test, 4 assertion)


Создание первого теста: юнит-тесты или Unit-Tets


Если предыдущие тесты смотрели на ваше приложение в целом: от точки входа, до точки выхода. То модульные тесты — помогают разложить все по полочкам, дав возможность тестировать каждый кирпичик, ака модуль, приложения.


Что тестировать и на сколько углубленно — на хабре довольно много статей. В каких-то ситуациях вам скажут, что обязательно тестировать полностью все методы и классы. В иных — разговор будет немного иным. Например, мне бросилась в глаза эта статья: Трагедия стопроцентного покрытия кода


У себя я протестирую класс, который работает по патерну ActiveRecord: загружу данные из массива и запущу валидацию. Если впоследствии добавится какое-либо обязательное поле и, как везде водится, про это все забудут: тест сразу выскажет свое возражение.


Вторым этапом я буду тестировать один из своих хелперов. Идея больше показательная, чем полезная.


Начало уже привычно:


tests/unit.suite.yml — правим согласно своего фреймворка(если вы его используете).
$ cept build — после внесения правок в файлы конфигурации всегда необходима пересборка.
$ cept generate:test unit SmokeUnit — создаем пустышку, для будущего теста.


Полный листинг моего теста
<?php

namespace tests\unit;

use common\helpers\JsonRhHelper;
use frontend\modules\wot\models\WotMonitoringClan;

class SmokeUnitTest extends \Codeception\Test\Unit {
    /**
     * @var \UnitTester
     */
    protected $tester;

    /**
     * @var array - в дальнейшем рефакторинг в полноценную фикстуру
     */
    protected $clanFixture = [
        'WotMonitoringClan' => [
            'clan_id' => 1,
            'status'  => '1',
            'name'    => 'DNO',
            'tag'     => 'Рачистая местность'
        ]
    ];

    protected function _before() {
    }

    protected function _after() {
    }

    /**
     * тестируем, что модель клана ведет себя корректно
     */
    public function testCheckClanAR() {
        $clan = new WotMonitoringClan();
        $clan->load($this->clanFixture);

        // по-умолчанию фикстура содержит корректные данные
        $this->assertTrue($clan->validate());
        // проверю собственные геттеры
        $fullClanTag = '[' . $clan->tag . ']';
        $this->assertTrue($clan->fullClanTag === $fullClanTag);
        // проверю, что ломается там, где должно
        $clan->clan_id = null;
        $this->assertFalse($clan->validate());
    }

    /**
     * проверяем, что хелпер JSONa отдает данные в стандартном формате
     */
    public function testStaticHelper() {
        // успешный ответ
        $expectedArray = ['success' => 'myTest'];
        $respArray     = JsonRhHelper::success('myTest', false);
        $this->assertTrue($expectedArray === $respArray);

        // не успешный ответ
        $expectedArray = [
            'error' => [
                'message' => 'myTest',
                'id'      => 13255
            ]
        ];
        $respArray     = JsonRhHelper::error('myTest', 13255, false);
        $this->assertTrue($expectedArray === $respArray);
    }

}

$ cept run unit — запускаем тест на выполнение. Видим OK (2 tests, 5 assertions)


Немного поясню:
$this->assertTrue($clan->validate()); — как следует из названия: ожидает, что в переменной или результате метода содержится логическое TRUE. Противовес: $this->assertFalse()
$this->assertEquals(1, count($myArray)); — ожидает равенство двух параметров


Т.е. парой проверок можно сделать какие-то базовые вещи. Подстелить себе соломинку, так сказать. А на досуге почитать про остальные выражения проверок: http://www.phpunit.de/manual/3.4/en/api.html#api.assert


Кстати! Теперь, когда мы создали все тесты, и наше приложение готово начать жить по-новому. Когда тесты уже будут запускаться 1 раз перед выкаткой. Скажу, о чем не упомянул в начале: после параметра run можно не указывать тип тестов. Тогда будут выполняться все типы тестов по очереди: $ cept run

В заключение


Конечно в этой статье все упрощено и написано поверхностно. Но, сами понимаете, что данная тема — это не одна статья. Уже не говоря про те самые Best-Practices, которые или набиваются своими шишками, или ты успеешь про них прочесть заранее.


Поэтому давайте попробуем начать в данном формате и посмотреть, что из этого выйдет.


Если искомая аудитория будет найдена — я обязательно постараюсь рассказать об опущенных мной Mock-обьектах, Fixtures, тестовых БД с дампами и еще много интересного, что используется в этом направлении.


Поделиться с друзьями
-->

Комментарии (8)


  1. HotFazer
    02.06.2017 08:38

    Спасибо, замотивировал попробовать codeception в деле!


    1. lgXenos
      02.06.2017 08:39

      Пожалуйста.
      :)


  1. simplewhite
    02.06.2017 19:43

    Мы используем codeception с Yii2. Очень удобно что мы можем использовать разные системы для acceptance тестов без изменения самих тестов


  1. malder001
    03.06.2017 10:13

    Функциональные тесты немного сходны с приемочными. Но в отличие от последних — не требуется запускать веб-сервер

    Возможно вы имеете в виду веб-драйвер?


    1. lgXenos
      03.06.2017 10:40

      Если быть очень точным — то отсутствие их обоих: и WebServer и WebDriver. Я нарисовал схему, согласно которой, я понимаю данный процесс:
      image
      Т.е. приемочные тесты — они идут как обычный пользователь (WebDriver) на сайт (WebServer). Там управление переходит в index.php, который в свою очередь уже запускает наше приложение.
      Функциональные же — берут особый конфиг приложения и сами запускают с его помощью наше приложение.


  1. xoma
    07.06.2017 17:23

    Спасибо за ссылку на доку Ru, нужно бы ее обновить и актуализировать.


    1. lgXenos
      09.06.2017 00:06

      Согласен.
      Но, к сожалению, это все, что я смог найти на скорую руку в просторах мировой сети.
      По дате последнего обновления можно предположить, что авторы просто уволились и забили на этот проект.
      Так что — хоть так
      :)


      1. xoma
        09.06.2017 10:21

        Автор — это я и мои друзья-коллеги =)
        Постараюсь актуализировать документацию, если есть желание — давайте вместе.