Тестируем с помощью CodeCeption
CodeCeption отличный PHP фреймворк для обеспечения проекта авто-тестами (Unit, Behaviour, Acceptance). CodeCeption использует 2 драйвера для покрытия тестами WEB интерфейса - guzzlehttp/guzzle и facebook/webdriver (через Selenium2). В отличие от не менее популярного фреймворка для тестирования по BDD методологии - Behat, CodeCeption не использует промежуточный слой абстракции - Behat/Gherkin, для обеспечения человеко-понятного описания шагов тестирования. Лично мне, как разработчику, эта абстракция всегда казалась избыточной (хотя решение довольно интересное). Долго расписывать не стану, рекомендую попробовать)
Особенности CodeCeption
Ключевой концепцией CodeCeption является представление тестов, как набор действий человека.
- Лаконичные «говорящие» тест-методы;
- Много-пользовательские (сессионные) тесты, которые могут выполняются в разных браузерах;
- WebDriver (от Facebook) вместо Mink (в отличие от Behat), и устоявшийся Selenium2 Standalone Server;
Типы тестов
Acceptance
Приемочные тесты. Эмулируют поведение реальных пользователей на сайте. Приемочные тесты не выполняются в testing окружении!
Functional
Функциональные тесты. Тестируем швы модулей приложений.
Unit
Unit-тесты. Тестируем логику модулей (классов) на самом низком уровне.
Подключение фреймворка
Подключаем пакет CodeCeption к проекту через Composer:
composer.phar require "codeception/codeception": "2.0.4@dev"
Примечание
Версия CodeCeption 2.0 требует в качестве зависимости phpunit 4.0 - будьте готовы к этому, если некоторые ваши пакеты строго требуют phpunit 3.7. В моем проекте был лишь один внутренний пакет, у которого в зависимостях был phpunit 3.7, я решил этот вопрос, расширив версионные ограничения самого пакета в composer.json:
"require": {
"phpunit/phpunit": "3.7.*|~4.0"
}
Подключение Selenium2 Standalone Server
Для использования WebDriver рекомендую подключить в зависимостях Selenium2 Standalone Server. Вы можете подключить пакет с последней версией Selenium Standalone Server 2.42.2 следующей командой:
composer.phar require "emagister/selenium-server:2.42.2@dev"
Создание тестового окружения
Внимание!
Далее я буду использовать сокращенный синтаксис (alias) для вызова исполняемого CodeCeption файла - codecept
, вместо набора полной команды - vendor/bin/codecept
.
Выполните следующую команду для генерации структуры каталогов для тестов и вспомогательных файлов:
codecept bootstrap -a Engineer app/tests/codeception
Примечание
Тут же рекомендую ознакомится с руководством http://allframeworks.ru/post/codeception-2-0-alpha.html, которое дает понимание по выбору актера на запрос: "Select an actor. Default: Guy". Вместо Engineer вы вправе указать подходящее для вашего случая имя актера, например: Client, Manager, Admin, ApiClient.
Команда инициализации создаст следующую структуру файлов и каталогов в указанной вами директории.
Файловая структура
app/tests/codeception Корневой каталог фреймворка
├── codeception.yml Основной файл конфигурации
└── tests Каталог с тестами
├── _bootstrap.php Общий файл инициализации
├── unit.suite.yml Конфигурация Unit-тестов
├── unit Каталог с Unit-тестами
│ ├── _bootstrap.php Файл инициализации при запуске Unit-тестов
│ └── CodeEngineer.php Базовый класс с общими методами необходимыми в Unit-тестах
├── functional.suite.yml Конфигурация модулей функциональных тестов
├── functional Каталог с функциональными тестами
│ ├── _bootstrap.php
│ └── TestEngineer.php
├── acceptance.suite.yml Конфигурация модулей приемочных тестов
├── acceptance Каталог с приемочными тестами
│ ├── _bootstrap.php Файл инициализации при запуске приемочных тестов
│ ├── WebEngineer.php Базовый класс с кастомными тестовыми методами
│ └── ManageUserCest.php Класс тестирует интерфейс управления пользователями
├── _data Каталог для файлов, необходимых при тестировании
│ ├── database.dump.sql Дамп БД для восстановления тестового окружения приложения
│ ├── books.csv Вы можете держать здесь любые необходимые для тестов файлы..
│ └── testing.png Например, изображения...
├── _helpers
│ ├── CodeHelper.php
│ ├── TestHelper.php
│ └── WebHelper.php
└── _log Логи, отчеты, скриншоты о прохождении тестов
├── report.json
├── report.tap.log
└── ManageUserCest.create.fail.png
Глобальная конфигурация
actor: Engineer
paths:
tests: tests
log: tests/_log
data: tests/_data
helpers: tests/_helpers
settings:
bootstrap: _bootstrap.php
colors: true
memory_limit: 1024M
log: true
modules:
config:
Db:
dsn: 'pgsql:host=localhost;dbname=DBNAME'
user: 'USER'
password: 'PASS'
dump: tests/_data/database.dump.sql
populate: true
cleanup: true
Примечание
Для успешного запуска запуска acceptance тестов нужно указать базовый URL сайта в конфиге приемочных тестов tests/acceptance.suite.yml:
# Конфигурация приемочных тестов.
# Запуск тестов в браузере с помощью WebDriver или PhpBrowser.
# If you need both WebDriver and PHPBrowser tests - create a separate suite.
class_name: WebEngineer
modules:
enabled:
- PhpBrowser
- WebHelper
- WebDriver
- Db
config:
PhpBrowser:
url: 'http://localhost:8000/'
WebDriver:
url: 'http://localhost:8000/'
browser: 'firefox'
wait: 2
После этого примените конфигурацию:
codecept build -c app/tests/codeception
Запуск тестов в браузере Chrome
Для запуска тестов в браузере Chrome необходимо скачать бинарник драйвера chromedriver. Распакуйте архив и...
- Укажите путь к каталогу с драйвером в переменной окружения
PATH
. - Скопируйте файл драйвера в системный каталог, например /usr/bin:
sudo cp chromedriver /usr/bin/chromedriver sudo chmod 755 /usr/bin/chromedriver
- Запустить Selenium Server с параметром:
java -jar selenium-server.jar -Dwebdriver.chrome.driver=/path/to/chromedriver
После этого перезапустите Selenium Server. Возможно потребуется перезапустить консоль (если будете прописывать путь в PATH).
Использование
Генерация пустого теста
Для генерации костяка пустого класса (или скрипта) приемочного теста есть команда generate:cest
:
codecept generate:cept -c app/tests/codeception acceptance ManageUserCept # тест-скрипт
codecept generate:cest -c app/tests/codeception acceptance ManageUserCest # тест-класс
Правила описания тестов
Каждый тест обязан содержать метод wantTo(), в котором вы описываете суть теста: "Я хочу выполнить такое-то действие, после чего увидеть такой-то результат".
$I = new WebEngineer($scenario);
$I->wantTo('see User list');
$I->amOnPage('/users');
// .. далее идут действия
Запуск тестов
Для запуска тестов выполните:
codecept run -c app/tests/codeception/ acceptance WelcomeCept.php
Опции run
--config (-c) Путь к проекту с тестами тестам
--report Show output in compact style
--html Generate html with results
--xml Generate JUnit XML Log
--tap Generate Tap Log
--json Generate Json Log
--colors Use colors in output
--no-colors Force no colors in output (useful to override config file)
--silent Only outputs suite names and final results
--steps Show steps in output
--debug (-d) Show debug and scenario output
--coverage Run with code coverage
--no-exit Don't finish with exit code
--group (-g) Groups of tests to be executed (multiple values allowed)
--skip (-s) Skip selected suites (multiple values allowed)
--skip-group Skip selected groups (multiple values allowed)
--env Run tests in selected environments. (multiple values allowed)
--verbose (-v|vv|vvv) Уровень отладки
Методы тестирования
Вывод сообщений
Вывод отладочной информации при запуске тестов с параметром --debug:
codecept_debug('текст или массив'); // ф-ция может быть вызвана в любом месте
$this->debug('текст или массив'); // метод debug доступен в Helper классах
Сообщение в консоль об ожидаемом результате:
$I->expect('что будет такой-то результат');
// * I expect что будет такой-то результат
Вывод комментария, просто текст:
$I->comment('⚑ Проверка блока «Панель навигации»');
// * ⚑ Проверка блока «Панель навигации»
Отправка запросов
Посетить страницу:
$I->amOnPage('/');
Отправить AJAX запрос:
$I->sendAjaxPostRequest('/path', ['param' => 'value']);
Отправить форму:
$I->submitForm('.css', [
'settings[value][]' => 'test'
]);
Манипуляция DOM элементами
$I->fillField('email', 'email'); // Заполнить поле
$I->appendField('#pass', 'pass'); // Добавить текст в поле
$I->checkOption('#css'); // Отметить чекбокс
$I->attachFile('input[type="file"]', 'file.xls');
$text = $I->grabTextFrom('#email'); // Получить текст элемента
События
Нажать на элемент (ссылку, кнопку):
$I->click('.css');
Нажать клавишу:
$I->pressKey('.css', WebDriverKeys::ENTER);
Методы проверки
Строгие и не строгие методы:
$I->see('text'); // Строгий метод проверки, наличие текста ОБЯЗАТЕЛЬНО
$I->canSee('text'); // Не останавливать проверку, если текст отсутствует
Методы проверки состояния:
$I->seeInTitle('title');
$I->seeCurrentUrlEquals('/path');
$I->seeEquals('text');
$I->seeContains('text');
$I->dontSee('text');
$I->seeLink('name');
$I->seeElement('.css');
$I->seeCheckboxIsChecked('#css');
$I->seeInField('field[name]', 'text');
Таймаут
Ждать 5 сек ИЛИ до выполнения условия (until):
$this->webDriver->wait(5)->until(function(RemoteWebDriver $web) {
$el = $web->findElement(WebDriverBy::className('expect-class'));
WebDriverExpectedCondition::visibilityOf($el);
});
Приостановить сценарий для отладки теста (работает только в --debug режиме):
$I->pauseExecution();
Сессии
Выполнить действия в отдельной сессии. Создание собственного экземпляра $I и параллельной сессии:
$I = new WebEngineer($scenario);
$nick = $I->haveFriend('nick');
// ..или
$I->does(function(WebGuy $nick) {
$nick->click('Milestones');
$nick->canSeeInTitle('Milestones');
});
Selenium
$I->executeInSelenium(function(RemoteWebDriver $web) {
$web->getKeyboard()->pressKey(WebDriverKeys::ENTER);
$driver->executeScript('alert('OK');');
$rows = $driver->findElements(WebDriverBy::cssSelector('table tr'));
});
WebHelper
namespace Codeception\Module;
use Codeception\Module;
use Codeception\TestCase;
use RemoteWebDriver;
use WebDriverBy;
class WebHelper extends Module
{
/**
* @var \RemoteWebDriver
*/
private $webDriver;
public function _before(TestCase $test)
{
$this->webDriver = $this->getWebDriver();
$this->webDriver->manage()->window()->setPosition(new \WebDriverPoint(320, 200));
}
private function getWebDriver()
{
/** @var \Codeception\Module\WebDriver $webDriver */
$webDriver = $this->getModule('WebDriver');
return $webDriver->webDriver;
}
/**
* @param string $selector
*
* @return \WebDriverElement
*/
public function findOne($selector)
{
$marker = 'marker-'.md5(uniqid(rand(), true));
$script = sprintf('$("%s").addClass("%s")', addslashes($selector), $marker);
$this->webDriver->executeScript($script);
return $this->webDriver->findElement(WebDriverBy::className($marker));
}
}
С версии Codeception 2.0 больше не используется 2-х фазное выполнение тестов!
Codeception выполняет каждый сценарий два раза - первый раз для анализа, а второй для исполнения. Любой PHP код, в тестовом сценарии будет выполнен два раза! Вы можете указать стадию, на которой этот код должен быть выполнен:
if ($scenario->preload()) {
// Стадия анализа
Logger::log('analyzing');
}
if ($scenario->running()) {
// Стадия выполнения теста
Logger::log('running');
}
CodeCoverage
Проверить уровень покрытия кода тестами:
// ... продолжение следует
#СodeСeption, #phpunit, #BDD, #Unit-тесты, #Selenium, #UI-тесты