Тестируем с помощью CodeCeption

Категория: PHP

CodeCeption отличный PHP фреймворк для обеспечения проекта авто-тестами (Unit, Behaviour, Acceptance). CodeCeption использует 2 драйвера для покрытия тестами WEB интерфейса - guzzlehttp/guzzle и facebook/webdriver (через Selenium2). В отличие от не менее популярного фреймворка для тестирования по BDD методологии - Behat, CodeCeption не использует промежуточный слой абстракции - Behat/Gherkin, для обеспечения человеко-понятного описания шагов тестирования. Лично мне, как разработчику, эта абстракция всегда казалась избыточной (хотя решение довольно интересное). Долго расписывать не стану, рекомендую попробовать)

Особенности CodeCeption

Ключевой концепцией CodeCeption является представление тестов, как набор действий человека.

  1. Лаконичные «говорящие» тест-методы;
  2. Много-пользовательские (сессионные) тесты, которые могут выполняются в разных браузерах;
  3. 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. Распакуйте архив и...

  1. Укажите путь к каталогу с драйвером в переменной окружения PATH.
  2. Скопируйте файл драйвера в системный каталог, например /usr/bin:
    sudo cp chromedriver /usr/bin/chromedriver
    sudo chmod 755 /usr/bin/chromedriver
  3. Запустить 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-тесты

категория: PHP