Drupal → Автоматизированное тестирование готового сайта с помощью Codeception

04.07.2020

Введение

Codeception — это популярный фреймворк для тестирования веб-приложений. Он написан поверх PHPUnit и позволяет более элегантно писать тесты используя методологию BDD.

Поддерживает три вида тестирования:

Unit tests (модульные тесты) — тестирование отдельных php-классов.

Functional tests (функциональные тесты) — тестирование приложения без использования веб-сервера и без поддержки javascript.

Acceptance tests (приёмочные тесты) — тестирование с использованием браузера, имитирует действия пользователя, поддерживает javascript. Этот вид тестирования интересует нас в первую очередь.

Пример приёмочного теста:

$I->amOnPage('/user');
$I->fillField('name', 'admin');
$I->fillField('pass', 'qwerty');
$I->click('Log in');
$I->see('admin', 'h1');

Довольно просто и наглядно.

Приёмочные тесты могут запускаться двумя способами:

Модуль PhpBrowser — эмулятор браузера на php, использующий Guzzle и Symfony BrowserKit. Выполняется быстро, но не поддерживает javascript.

Модуль WebDriver — полноценный браузер Chrome, который может работать в headless (невидимом) режиме.

Установка и настройка

Ставим Codeception:

composer require codeception/codeception --dev

Инициализируем:

vendor/bin/codecept bootstrap

В процессе скрипт попросит разрешение на выполнение composer update и нужно согласиться, иначе всё сломается.

После инициализации в корне появится файл codeception.yml и папка tests, а в composer.json добавится два пакета — codeception/module-phpbrowser и codeception/module-asserts.

В файле tests/Acceptance.suite.yml изменяем опцию url на адрес тестируемого сайта:

actor: AcceptanceTester
modules:
    enabled:
        - PhpBrowser:
            url: https://example.com

Все файлы и папки, связанные с unit и functional тестами можно удалить.

Первый тест на PhpBrowser

В папке tests/Acceptance создаём php файл с названием теста и суффиксом Cest, например FrontPageCest.php:

<?php

namespace Tests\Acceptance;

use Tests\Support\AcceptanceTester;

class FrontPageCest {

  public function testFrontPage(AcceptanceTester $I) {
    $I->amOnPage('/');
    $I->seeElement('body.path-frontpage');
  }

}

Cest — это формат тестов, в котором каждый тест это публичный метод класса. Есть ещё старый формат cept, где тест это просто php файл с набором команд.

Для запуска всех тестов выполняем:

vendor/bin/codecept run

Запуск одного cest файла:

vendor/bin/codecept run acceptance FrontPageCest

Запуск конкретного cest теста:

vendor/bin/codecept run acceptance FrontPageCest:testFrontPage

Скриншот работы Codeception:

Документация по запуску тестов.
Документация по модулю PhpBrowser.

Создание тестового окружения

Как только тесты буду изменять данные на сайте (создавать/удалять ноды, отправлять формы, заливать файлы) встанет необходимость отдельного тестового окружения, т.е. своей БД, своей директории под загружаемые файлы и своих настроек.

Здесь вариантов несколько, но самый простой это стандартный мультисайтинг, тем более по такому же принципу работают друпаловские тесты:

  1. Создаём папку sites/test

  2. Копируем туда settings.php и services.yml из sites/default

  3. В sites/test/settings.php изменяем $settings['file_public_path'], $settings['file_private_path'] и $settings['file_temp_path'] если они указаны.

  4. Клонируем БД и прописываем данные новой базы в sites/test/settings.php.

  5. Создаём отдельный домен и направляем его в тестируемый сайт.

  6. Переименовываем файл sites/example.sites.php в sites/sites.php и добавляем в конец:

    $sites['test.example.com'] = 'test';

    Где вместо test.example.com пишем название своего тестового домена.

Теперь при открытии http://test.example.com друпал будет брать настройки из sites/test/settings.php и заливать файлы в sites/test/files вместо sites/default/files.

Чтобы каждый раз перед старом теста вручную не клонировать базу поможет модуль Db:

  1. Ставим модуль:

    composer require codeception/module-db --dev
  2. Правим tests/Acceptance.suite.yml:

    actor: AcceptanceTester
    modules:
        enabled:
            - PhpBrowser:
                url: https://test.example.com
            - Db:
                dsn: 'mysql:host=localhost;dbname=testing'
                user: 'root'
                password: 'root'
                dump: 'tests/_data/dump.sql'
                populate: true
                cleanup: false
                reconnect: false
                waitlock: 0
                populator: 'vendor/bin/drush sql-dump --result-file=$dump && mysql --user=$user --password=$password --host=$host $dbname < $dump'

Теперь при каждом запуске тестов codeception будет с помощью drush делать актуальный дамп в tests/_data/dump.sql, очищать базу testing и импортировать туда данные из дампа.

Важное замечание — на Windows во всех файловых путях надо использовать обратный слэш \, т.е.:
— не vendor/bin/drush, а vendor\bin\drush
— не tests/_data/dump.sql, а tests\_data\dump.sql
Здесь все примеры написаны для Linux с использованием /, поэтому надо быть внимательным!

Документация по модулю Db.

Тестирование в реальном браузере Chrome

Для тестирования в реальном браузере понадобится модуль WebDriver, который реализует общение между codeception и браузером по одноимённому протоколу WebDriver.

Модуль умеет запускать тесты с помощью Selenium, ChromeDriver, PhantomJS, SauceLabs, BrowserStack и TestingBot. Самый простой способ это ChromeDriver — независимый сервер реализующий протокол WebDriver для хрома, сервер разрабатывается командой Chromium, может работать как самостоятельно, так и как часть Selenium WebDriver.

Ставим модуль:

composer require codeception/module-webdriver --dev

Скачиваем ChromeDriver для своей ОС и установленной версии Chrome (да, после каждого мажорного обновления Chrome придётся обновлять ChromeDriver). Распаковываем. Добавляем путь к бинарнику в PATH.

Изменяем Acceptance.suite.yml:

actor: AcceptanceTester
modules:
    enabled:
        - WebDriver:
            url: 'http://test.example.com/'
            window_size: false
            port: 9515
            browser: chrome
            capabilities:
                'goog:chromeOptions':
                    args: []
        - Db:
            dsn: 'mysql:host=localhost;dbname=testing'
            user: 'root'
            password: 'root'
            dump: 'tests/_data/dump.sql'
            populate: true
            cleanup: false
            reconnect: false
            waitlock: 0
            populator: 'vendor/bin/drush sql-dump --result-file=$dump && mysql --user=$user --password=$password --host=$host $dbname < $dump'
extensions:
    enabled:
        - Codeception\Extension\RunProcess:
            - 'chromedriver --url-base=/wd/hub --port=9515'

Теперь тесты будут выполнятся в реальном браузере Chrome.

Методы для модуля WebDriver немного отличаются от PhpBrowser, например see() ищет только видимые элементы, и если элемент скрыт с помощью display:none; то тест выдаст ошибку. Так же в WebDriver есть методы, отсутствующие в PhpBrowser, например seeElementInDOM(), seeInPopup(), wait(), executeJS() и т.д.

Документация по модулю WebDriver.

Лайфхаки

Очистка тестового окружения перед стартом теста

extensions:
    enabled:
        - Codeception\Extension\RunBefore:
            - 'vendor/bin/codecept clean'
            - 'rm -rf sites/test/files/*'

Отключение drupal модулей мешающих тестированию

extensions:
    enabled:
        - Codeception\Extension\RunBefore:
            - 'vendor/bin/drush pm-uninstall captcha backup_migrate automated_cron --uri=test.example.com -y'

Смена пароля администратора в тестовом окружении

extensions:
    enabled:
        - Codeception\Extension\RunBefore:
            - 'vendor/bin/drush user-password admin qwerty --uri=test.example.com'

Просмотр логов после неудачного выполнения теста

После краха теста в папке tests/_output будет html код страницы и сриншот. Самостоятельно сохранить код можно с помощью $I->makeHtmlSnapshot(), а скриншот с помощью $I->makeScreenshot().

Вынос часто повторяющихся действий в свой метод

Код можно выносить в файл tests/Support/AcceptanceTester.php. Например аутентификация:

class AcceptanceTester extends \Codeception\Actor {

  use _generated\AcceptanceTesterActions;

  /**
   * Login as $username.
   */
  public function login($username, $password) {
    $this->amOnPage('/user');
    $this->fillField('name', $username);
    $this->fillField('pass', $password);
    $this->click('#edit-submit');
  }

}

Использование в cest файле:

public function testName(AcceptanceTester $I) {
  $I->login('admin', 'qwerty');
  ...
}

Запросы в тестовую базу данных

Добавляем в tests/Support/Helper/Acceptance.php:

/**
 * Sql query.
 */
public function sqlQuery($query){
  return $this->getModule('Db')->_getDbh()->query($query);
}

Добавляем в Acceptance.suite.yml:

modules:
    enabled:
        ...
        - \Helper\Acceptance

Выполняем в консоли:

vendor/bin/codecept build

Использование в cest файле:

$last_inserted_nid = $I->sqlQuery("SELECT MAX(nid) FROM node")->fetchColumn();

Запуск тестов из PhpStorm

Документация на сайте JetBrains.

Пример настройки для Windows:

Очерёдность запуска тестов

Тесты в рамках cest файла выполняются в порядке следования и на это повлиять нельзя. Сами cest файлы выполняются по алфавиту и на это тоже повлиять нельзя, кроме как добавлять в название порядковый номер:

Test01FooCest.php
Test02BarCest.php
Test03BazCest.php

Продвинутый amOnPage()

У меня вместо amOnPage() есть хелпер amOnDrupalPage(), открывающий страницу и проверяющий её на наличие друпаловских ошибок:

tests/Support/AcceptanceTester.php

/**
 * Goto to page and check errors.
 */
public function amOnDrupalPage($url) {
  if (strpos($url, '://') === FALSE) {
    $this->amOnPage($url);
  }
  else {
    $this->amOnUrl($url);
  }
  $this->seeElementInDOM('body');
  $this->dontSeeElement('.messages--error');
  $this->seeNumRecords(0, 'watchdog', ['type' => 'php']);
}

Выполнить js и получить результат

$currentPath = $I->executeJS("return drupalSettings.path.currentPath;");

Получить адреса всех ссылок в блоке

$urls = $I->grabMultiple('.block-name a', 'href');

Хелпер для проверки хлебных крошек

// tests/Support/AcceptanceTester.php

/**
 * See breadcrumb.
 */
public function seeBreadcrumb(array $items) {
  foreach ($items as $key => $text) {
    $this->see($text, '.breadcrumb li:nth-child(' . ($key+1) . ')');
  }
}

Использование:

$I->amOnPage('/catalog/auto');
$I->seeBreadcrumb(['Главная', 'Каталог', 'Авто-товары']);

Использование функций assert* в тестах

Включаем модуль Asserts:

# Acceptance.suite.yml

modules:
    enabled:
        ...
        - Asserts

Делаем билд:

vendor/bin/codecept build

Используем:

class SiteCest {

  public function testFirst(AcceptanceTester $I): void {
    $result = 2*2;
    $I->assertEquals(4, $result);
  }

}

Вообще рекомендую пробежаться по официальной документации, чтобы понять возможности Codeception.

Написанное актуально для
Codeception 5
Похожие записи

Комментарии

Спасибо, как всегда понятно и доходчиво.
я на WebDriver писал, и признаться мне очень надоело базу востанавливать

Спасибо, полезная статья.
На очередность выполнения тестов можно влиять с помошью директивы @depends в анотации к методу https://codeception.com/docs/07-AdvancedUsage#Dependencies
Работает в том числе и для задания очередности выполнения классов (cest файлов).

Добавить комментарий