Настройка тестирования в 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тавим Composer
    curl -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-нотации. Подробнее:

[1oo%, EoF]
Похожие материалы:
Yii. Связи таблиц в стиле Active Record

Понравилась статья? Расскажите о ней друзьям:


Комментарии
Для работы модуля комментариев включите javaScript


Показать/скрыть правила
Имя
[i] [b] [u] [s] [url]
:-) ;-) :D *lol* 8-) :-* :-| :-( *cry* :o :-? *unsure* *oops* :-x *shocked* *zzz* :P *evil*

Осталось 1000 символов.
Код защиты от спама Обновить код
Каждый комментарий проходит ручную модерацию. 100% фильтрация спама.
Продвижение
Время
Метки