Целью данной статьи я ставил показать людям, не знакомым с тестированием, как можно действительно быстро начать тестировать, собрав все в одном месте с минимумом воды и на русском языке. Пусть это будет весьма примитивно. Пусть не очень интересно людям, которые уже живут по 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.4.2"
Обратите внимание, что версию надо вписывать ту, которая будет отображена на сайте в момент прочтения этой статьи.
Финальная настройка перед началом работы — установить себе 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
- В файле
tests/_bootstrap.php
добавляем константу:defined('YII_ENV') or define('YII_ENV', 'test');
. Если файла нет — создаем и добавляем в корневойcodeception.yml
settings.bootstrap:_bootstrap.php. Такие файлы необходимо будет вкладывать во все папки с тестами. Если забудете — Codeception вам об этом напомнит. - В папке config yii-приложения, рядом с
main.php
кладемtest.php
, который заполняем из мануала - Экспертным путем обнаружил, что модуль не видит мои 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
Если предыдущие тесты смотрели на ваше приложение в целом: от точки входа, до точки выхода. То модульные тесты — помогают разложить все по полочкам, дав возможность тестировать каждый кирпичик, ака модуль, приложения.
Что тестировать и на сколько углубленно — на хабре довольно много статей. В каких-то ситуациях вам скажут, что обязательно тестировать полностью все методы и классы. В иных — разговор будет немного иным. Например, мне бросилась в глаза эта статья: Трагедия стопроцентного покрытия кода post-link
У себя я протестирую класс, который работает по патерну 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, тестовых БД с дампами и еще много интересного, что используется в этом направлении.