Drupal → Автоматизированное тестирование готового сайта с помощью Codeception
Введение
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: http://example.com
Все файлы и папки, связанные с unit и functional тестами можно удалить.
Первый тест на PhpBrowser
В папке tests/acceptance
создаём php файл с названием теста и суффиксом Cest
, например FrontPageCest.php
:
<?php 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.
Создание тестового окружения
Как только тесты буду изменять данные на сайте (создавать/удалять ноды, отправлять формы, заливать файлы) встанет необходимость отдельного тестового окружения, т.е. своей БД, своей директории под загружаемые файлы и своих настроек.
Здесь вариантов несколько, но самый простой это стандартный мультисайтинг, тем более по такому же принципу работают друпаловские тесты:
-
Создаём папку
sites/test
-
Копируем туда
settings.php
иservices.yml
изsites/default
-
В
sites/test/settings.php
изменяем$settings['file_public_path']
,$settings['file_private_path']
и$settings['file_temp_path']
если они указаны. -
Клонируем БД и прописываем данные новой базы в
sites/test/settings.php
. -
Создаём отдельный домен и направляем его в тестируемый сайт.
-
Переименовываем файл
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:
-
Ставим модуль:
composer require codeception/module-db --dev
-
Правим
tests/acceptance.suite.yml
:actor: AcceptanceTester modules: enabled: - PhpBrowser: url: http://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 с использованием /
, поэтому надо быть внимательным!
Тестирование в реальном браузере 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'
Теперь тесты будут выполнятся в реальном браузере Chrome.
Методы для модуля WebDriver немного отличаются от PhpBrowser, например see()
ищет только видимые элементы, и если элемент скрыт с помощью display:none;
то тест выдаст ошибку. Так же в WebDriver есть методы, отсутствующие в PhpBrowser, например seeElementInDOM()
, seeInPopup()
, wait()
, executeJS()
и т.д.
Важно помнить, что каждый тест (функция в cest файле) выполняется в новой песочнице Chrome, т.е. между тестами не сохраняются куки и адрес открытой страницы, поэтому в большинстве случаев самая первая строчка в тесте это $I->amOnPage(...)
.
Документация по модулю 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(['Главная', 'Каталог', 'Авто-товары']);
Вообще рекомендую пробежаться по официальной документации, чтобы понять возможности Codeception.
Комментарии
Спасибо, как всегда понятно и доходчиво.
я на WebDriver писал, и признаться мне очень надоело базу востанавливать
Спасибо, полезная статья.
На очередность выполнения тестов можно влиять с помошью директивы @depends в анотации к методу https://codeception.com/docs/07-AdvancedUsage#Dependencies
Работает в том числе и для задания очередности выполнения классов (cest файлов).
Оставить комментарий