Тестирование с Сodeception для чайников: 3 вида тестов

в 17:07, , рубрики: codeception, php, yii, yii2 и codeception, модульное тестирование, приёмочное тестирование, тестирование, Тестирование веб-сервисов, функциональное тестирование

Целью данной статьи я ставил показать людям, не знакомым с тестированием, как можно действительно быстро начать тестировать, собрав все в одном месте с минимумом воды и на русском языке. Пусть это будет весьма примитивно. Пусть не очень интересно людям, которые уже живут по 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(CodeceptionUtilHttpCode::OK);
$I->see('Главная блога'); // ! Тут часть фразы с вашей главной
$I->amOnPage('/about');
$I->seeResponseCodeIs(CodeceptionUtilHttpCode::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 testsunit;

use commonhelpersJsonRhHelper;
use frontendmoduleswotmodelsWotMonitoringClan;

class SmokeUnitTest extends CodeceptionTestUnit {
    /**
     * @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, тестовых БД с дампами и еще много интересного, что используется в этом направлении.

Автор: lgXenos

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js