Настройка тестирования в Yii 1.x
версия для печатиОкружение: KUbuntu 14.04.1 LTS, PHP 5.5.9, Yii 1.1.16. К этому делу нужно прикрутить последний PHPUnit и Selenium. Я вообще не был знаком с unit-тестами и функциональными тоже, поэтому для начала прочитал мануал в Yii, часть мана PHPUnit, кучу всего на хабре + форумы. И получилась каша :( К тому же в последствии оказалось, что Yii-ные инструкции устарели. Рассказываю, как настраивается окружение сейчас..
Ставим PHPUnit
Текущая стабильная версия PHPUnit 4.8. Для его установки потребуется Composer. Можно использовать PHPUnit из phar-архива, но с ним Yii некорректно работает и многое в ее движке придется исправить.
Ликбез по Composer:
Cтавим Composercurl -sS https://getcomposer.org/installer | php chmod +x composer.phar sudo mv composer.phar /usr/local/bin/composer
Далее, на сайте PHPUnit предлагается описать зависимость в composer.json в виде:
{ "require-dev": { "phpunit/phpunit": "4.7.*" } }Для Yii этого будет мало, см. ниже более подходящий конфиг. И кстати, "require-dev" тоже устарело. Пишем просто "require".
Там же было сказано про глобальную установку.
For a system-wide installation via Composer, you can run: composer global require "phpunit/phpunit=4.7.*" Make sure you have ~/.composer/vendor/bin/ in your path.Тут речь про домашний каталог, нужно убедиться в наличии иерархии. Я полагаю, что такая "глобальная" установка просто разместит пакет в моем домашнем каталоге, откуда его будут находить проекты, требующие PHPUnit.
Конфиг под Yii
{ "require": { "phpunit/phpunit": "~4.3", "phpunit/dbunit": "~1.3", "phpunit/phpunit-selenium": "~1.4", "phpunit/phpunit-story": "~1.0", "phpunit/php-invoker": "~1.1" } }Кладем файл composer.json в [protected/], получим установку PHPUnit локально, для текущего проекта.
Далее выполняем в каталоге [protected/]
vijit@Z710:/www/site.loc/protected/$ composer installПосле этого в каталоге [protected/vendor/] у нас появятся все необходимые файлы PHPUnit. Если проект поддерживается Гитом, добавляем в .gitignore все из этого каталога. Для проверки, что все получилось, можно запросить версию PHPUnit:
vijit@Z710:/www/site.loc/protected/$ vendor/bin/phpunit --versionСоздаем тест, заходим в [protected/tests/] и запускаем наш тест:
vijit@Z710:/www/site.loc/protected/tests/$ ../vendor/bin/phpunit unit/MyTest.phpНемного забегая вперед, нихрена мы тут не получим. Нужно допилить Yii, чтоб тесты заработали.
Или запуск функционального теста (требуется запущенный сервер Selenium)
vijit@Z710:/www/site.loc/protected/tests/$ ../vendor/bin/phpunit functional/someTest.phpСтавим Selenium
Selenium нужен для функциональных тестов. Написан на Java, т.о. для запуска потребуется наличие JRE (Java Runtime Environment).
С этим фреймворком добавляется много новых слов и заморочек. В частности, в краткой истории рассказано о слиянии Selenium и WebDriver. Сейчас Selenium состоит из некольких инструментов:
- Selenium 2, он же WebDriver - новая версия тестового движка. Поддерживается обратная совместимость со старой версией.
- Selemium 1, он же Selenium-RC - старая версия движка. Deprecated.
- Selenium IDE - (Integrated Development Environment) инструмент для создания тестов. Что-то типа записи макросов в MS Office. Не решает всех проблем, но помогает автоматизировать создание каркаса будущего теста.
- Selenium-Grid клевый инструмент для управления массивом тестов. Например, параллельный запуск или запуск с разными окружениями типа local|dev|production.
Есть два варианта тестирования через Selenium: используя только Selenium IDE или через запуск сервера и полным фаршем :) В первом случае достаточно скачать firefox-расширение "Selenium IDE" где-то отсюда и все! Пишем в нем тесты и радуемся жизни, даже в Yii ничего пилить не нужно. Второй вариант потребует некоторых усилий, описанных далее.
Selenium-сервер
WebDriver и Selenium Server. Если браузер и тесты находятся на одной машине, тогда Selenium Server не нужен (с). Однако нельзя скачать нечто отдельное, называемое "WebDriver", поэтому качаем то, что называется "Selenium Standalone Server".
Полученный jar-архив копируем, например, в [/etc/selenuim/]. Ставим права 0444 (только чтение). Запуск сервера:
vijit@Z710:~$ java -jar /etc/selenium/selenium-server-standalone-2.47.1.jar 12:08:44.207 INFO - Launching a standalone Selenium Server 12:08:44.265 INFO - Java: Oracle Corporation 24.79-b02 12:08:44.265 INFO - OS: Linux 3.13.0-62-generic amd64 12:08:44.279 INFO - v2.47.1, with Core v2.47.1. Built from revision 411b314 ... 12:08:44.498 INFO - RemoteWebDriver instances should connect to: http://127.0.0.1:4444/wd/hub 12:08:44.498 INFO - Selenium Server is up and running
Все, сервер готов к работе.
Фреймворк Selenium предоставляет API для работы с ним. Реализация клиентского кода к этому API зависит от языка. Называется "Language Bindings" в терминологии Селениума. На оф.сайте есть решения для разных языков. Для PHP тоже есть что-то с разной степенью документированности и конечно можно написать свое, предварительно скурив до буквы мануал по фреймворку :)
Драйверы WebDriver-а
Ага, все непросто :) "WebDriver" - это имя ключевого интерфейса, под который должны писаться тесты. Существует несколько реализаций этого интерфейса. В частности, HtmlUnit Driver - наиболее шустрая и легкая реализация WebDriver-а. Как предполагает название, этот драйвер основан на HtmlUnit. HtmlUnit - java-реализация WebBrowser без GUI. Для любых языков, использующих API Селениума (отличных от java), Selenium Server требует использовать этот драйвер. Перевод этого текста.
Как будет выглядеть использование HtmlUnit. Запускаем тест в консоли, никакие окна браузеров не появляются, но тест работает.. или не работает :). Замечу, что есть отдельные драйвера для Firefox, IE, Chrome и др. Так сказано в мануале, реально попытался найти плагин Огнелиса - бесполезно.
Yii. Добработка напильником
Примечание: есть вероятность, что я не все запомнил и описал. Доработка заняла много времени и не все действия были задокументированы.
Unit-тесты
Нужно в [protected/tests/bootstrap.php] добавить:
//@see http://mattmccormick.ca/2012/09/14/unit-testing-url-routes-in-yii-framework/
//@see http://stackoverflow.com/questions/11023668/yii-chttprequesterror-while-functional-unittesting-in-module
$_SERVER['SCRIPT_FILENAME'] = 'index-test.php';
$_SERVER['SCRIPT_NAME'] = '/index-test.php';
$_SERVER['REQUEST_URI'] = 'index-test.php';
Yii::createWebApplication($config); //это уже есть.
Без этой обманки, в unit-тестах Yii падает, "Компоненту CHttpRequest не удалось определить URI запроса". Это явно баг, т.к. в консоли движок вообще не должен парсить URI.
Настройка PHPUnit описана в файле [protected/tests/phpunit.xml]. В корневой секции конфига указан файл начальной загрузки
<phpunit bootstrap="bootstrap.php" ... >Вот как PHPUnit цепляется к Yii. Возможно эта особенность связана с таким багом:
vijit@Z710:/www/site.loc/protected/tests/unit$ ../../vendor/bin/phpunit MyTest.php //не работает vijit@Z710:/www/site.loc/protected/tests$ ../vendor/bin/phpunit unit/MyTest.php //работаетПредположение: вызывать PHPUnit нужно из каталога, где лежит phpunit.xml
Мелочи по настройке конфига Yii описывать не буду, все совпадает с мануалом. Мой конфиг сейчас выглядит так:
<?php
//test.php
return CMap::mergeArray(
require 'main.php',
[
'components' => [
'fixture' => ['class' => 'system.test.CDbFixtureManager'],
'db' => ['connectionString' => 'mysql:host=127.0.0.1; dbname=site_test'],
],
]
);
Функциональные тесты
Конфиг phpunit.xml содержит deprecated-настройки для Selenium. Удаляем их.
В корне сайта нужен index-test.php. Он незначительно отличается от index.php, в частности в нем подключается конфиг test.php. В комментарии index-test.php рекомендовано не выкладывать его на боевом серваке.
Пример функционального теста из коробки Yii предполагает наличие первой версии Selenium. А мы будем работать со второй. Переписать движок под свои хотелки сложно, используем готовое yii-расширение для работы с Selenium 2 - webdriver-test. Само расширение основано на одной из PHP-реализаций клиентского кода под Selenium API, именуемого PHPWebdriver. Сложная получилась мысль.. Суть в том, что документацию найти будет сложно, разработчик PHPWebdriver не парился по этой части..
Качаем расширение, распаковываем в [/protected/extentions/]. Переносим тестовый пример, ExampleTest.php, [/protected/tests/functional/]. Запускаем Selenium Server (если еще этого не сделали), запускаем тест:
vijit@Z710:/www/site.loc/protected/tests$ ../vendor/bin/phpunit functional/ExampleTest.php
Должен открыться Firefox, в нем страница Гугля с поиском. В окне консоли, где запущен сервер, пойдет лог. В окне с запущенным тестом - свой лог. Собственно дальше пилим свои тесты под свой сайт. Удачи..
Базовый класс функциональных тестов, [protected/tests/WebTestCase.php], у меня выглядит так:
/**
* Скрипт работает на функциональных (приемочных) тестах, но подключается так же в unit-тестах.
* Встроенный функционал безнадежно устарел. Вместо него использую расширение webdriver-test
* @see http://www.yiiframework.com/extension/webdriver-test
*/
//Yii::import( 'ext.webdriver-bindings.CWebDriverTestCase' ); //не работает
require_once __DIR__ . '/../extensions/webdriver-bindings/CWebDriverTestCase.php';
define('TEST_BASE_URL', 'http://site.loc/index-test.php'); //для тестов на локалке
class WebTestCase extends CWebDriverTestCase
{
/**
* Настройка перед выполнением любого функционального теста
*/
protected function setUp()
{
parent::setUp('localhost', 4444, 'htmlunit'); //chrome|firefox|htmlunit
$this->setBrowserUrl(TEST_BASE_URL);
}
}
Используя расширение webdriver-test мы отказываемся от запиленного в движок функционала Yii. Он устарел по всем параметрам. Однако и удалить лишнее не получится, т.к. все тесно связано. Проект превращается в кладбище :( Возможно в Yii 2.x ситуация тестами лучше. Есть другое решение: не использовать расширение webdriver-test, а используя [/vendor/phpunit/phpunit-selenium/] накатать свой супер-класс для функциональных тестов.. =)
Без документации мало чего можно написать в тестах. Покопался в исходниках, нашел следующее:
- список методов selenium-тестирования, SeleniumTestCase.php [/vendor/phpunit/phpunit-selenium/PHPUnit/Extensions/]
- Реализация методов, Driver::__call() [/vendor/phpunit/phpunit-selenium/PHPUnit/Extensions/SeleniumTestCase/]
- Немного про assert-ы тут
- Документация Selenuim RC на Java тут. Кроме всего прочего на странице интересующей функции есть краткое описание (если повезет).
Пример unit-теста
Тут я долго не мог сдвинуться с тестовой вариации "Hello, world!". Все от того, что слишком много прочитал. Коротко, вот как может выглядеть боевой тест в Yii:
метод для теста
/**
* Сервис "Блоги". Слой между контроллером и моделью. В Yii из коробки нет, сами добавили.
*/
class BlogsService extends StoryService
{
/**
* Другие статьи автора. Для страницы элемента
* @param int $id текущая статья
* @param int $author автор
* @return Blogs
*/
public function otherOfAuthor($id, $author)
{
$crit = [
'condition' => "blog_author_id = :author AND blog_id <> :id",
'order' => 'public_date DESC',
'limit' => 1,
'params' => [':id' => $id, ':author' => $author],
];
return $this->LoadItems($crit)->getItems();
}
}
тестирующий класс
/**
* название класса - от балды (хотя Yii не приветствует)
*/
class SomeTest extends CTestCase
{
/**
* Имя метода от фонаря, но реально тестируем BlogsService::OtherOfAuthor()
*/
public function testAnything()
{
//Arrange
$id = 10;
$dto->author = 4;
//Act
$svc = new BlogsService(); //объект тестируемого класса
$rslt = $svc->OtherOfAuthor($need, $dto); //дергаем тестируемый метод
//Assert
$this->assertCount($need, $rslt); //утверждениями проверяем, что получили
$this->assertInstanceOf('Blogs', $rslt[0]);
}
}
Вызов теста
vijit@Z710:/www/site.loc/protected/tests$ ../vendor/bin/phpunit unit/SomeTest.php
Названия тестовых классов и методов в них - это условности. Можно называть как угодно (в пределах допустимого фреймворками). Реально тесты работают не по названиям методов, по коду в них. Т.о. метод SomeClass::testAnything() должен содержать полезный код, чтоб было что тестировать. Я долго тупил на этом месте и искал некую мистическую связь между тестом и целевым методом через названия.. :) Наконец понял, как делать. Внутри метода создаем искусственные параметры, при необходимости описываем stubs/mocks, потом создаем объект тестируемого класса, вызываем тестируемый метод с передачей в него наших параметров. Результат проверяем через assert-ы. Вот и вся идея :)
Пример теста под Selenium
Собственно, почти рабочий пример есть в yii-расширении webdriver-test. И сама Yii так же предлагает пример приемочного теста. Я не буду переносить их сюда, потому что они простые, но есть разница в использовании методов. Предложу способ лучше: ставим Selenium IDE, это расширение Огнелиса. Лишние форматтеры удаляем после установки. Собираем тест в IDE, потом экспортируем в PHP через Selenium IDE: PHP Formatters (не ставится в комплекте с IDE) и разбираемся c кодом.
Полезно знать, что в Селениуме обращения к узлам DOM'а пишутся в XPath-нотации. Подробнее:
- Википедия. XPath
- примеры
- тестер Раз, тестер Два
- FirePath - дополнение Firebug. Анализ, редактирование и сборка выражений XPath 1.0.
Похожие материалы:
Yii. Связи таблиц в стиле Active Record
Понравилась статья? Расскажите о ней друзьям: