TDD — как тестировать HTML вывод? Регулярками?!
388
31.01.2011, в 00:22
Навеяно топиком "«Я не пишу юнит-тесты, потому что ...» — отговорки". Решил ещё раз попробовать использовать TDD.

Имеется некое MVC веб-приложение. Допустим, что модели и контроллеры тестами покрыты. А вот как тестировать вьюхи, шаблоны, лайауты, результатом работы которых является непосредственный вывод на stdin. Понятно, что вывод можно перехватить через ob_*, но вот как тестировать вообще HTML код (native PHP шаблоны)?

Попробовал так:
На главной странице должна быть ссылка на user/new (регистрация). Написал тест (PHPUnit):
  function testRegisterLinkForAnonymousPresent()
  {
    $app = new App();
    $this->expectOutputRegex("#<a.+href='/user/new'.*>.+</a>#");
    $app->run();
  }

Настроил вывод этой ссылки на главной — тест прошёл

Следующий шаг — по /user/new должна выводиться форма регистрации с action /user и методом post. Написал тест:
  function testRegisterFormPresentAndValid()
  {
    $app = new App();
    $request = new Request('/user/new');
    $app->setRequest($request);
    $this->expectOutputRegex("#<form.+action='/user'.+method='POST'.*></form>#");
    $app->run();
  }

Сделал форму, тест прошёл, но потом обратил внимание, что опечатался в шаблоне
<form action='/user'bmethod='POST'>
</form>

Начал думать над регуляркой и понял, что она ошибочна в принципе — атрибуты могут быть в другом порядке, после form обязателен пробел и т. п. начал рисовать двухэтажную, потом она плавно стала перетекать в трёхэтажную. А ведь ещё не дошёл до проверки полей, порядок которых тоже может быть произвольным. Собственно тут начал гуглить как выходят из положения, узнал много нового про TDD, но конкретно по сабжу ничего не нашёл толком.

Подскажите, пожалуйста, куда рыть?

Регулярками, имхо, не реально проверять сложные структуры. Пока два варианта:

— Проверять теги не целиком, а по частям: в одном тесте/ассерте проверить, что есть тег form в принципе, во втором, что в нём есть атрибут action с нужным значением, в третьем, что method есть с POST, в четвёртом, что есть тег input внутри form. Чтобы избегать трёхэтажных выражений можно параллельно с проверкой вырезать нужные куски, а уж потом проверять только их, например "#<form(.*)>(.*)#"
— Парсить HTML и проверять тэги/атрибуты/значения уже в дереве в объектном виде.

P.S. Это не функциональное тестирование, не смотря на то, что вроде бы проверяется всё приложение, просто так совпало, рефакторинг ещё не делал, чтобы проверить шаблон отдельно — проверять нужно именно работу юнита, результат которой HTML-код на наличие нужной комбинации тегов/атрибутов/значений/текста.

Updated:
В процессе ковыряния Зенда нашёл практически недокументированные функции PHPUnit assertSelectCount(), assertSelectEquals, assertSelectRegExp(), теперь тест выглядит так:
  function testRegisterLinkForAnonymousPresent()
  {
    ob_start();
    $app = new App();
    $app->run();
    $result = ob_get_contents();
    ob_end_clean();
    
    $this->assertSelectEquals('a[href="/user/new"]', 'Register', 1, $result);
  }


Наверное надо расширить класс PHPUnit_Extensions_OutputTestCase чтобы избавиться от ob_*
Ответы (5)
Сортировать по:
  • 1
    Можно валидатором проверить, тогда такой косяк показал бы.
  • 1
    вы можете использовать selenium он это умеет.
    Когда пользовался ZendFramework, то использовал компонент Zend_Dom_Query
    его можно так же использовать отдельно от ZF, поддерживает xpath и css запросы, это очень удобно относительно регулярок.
    framework.zend.com/manual/ru/zend.dom.query.html
  • 2
    DOM-методы, CSS/XPath селекторы. Регулярки не подходят для разбора html, это уже давно не новость.
  • 1
    gro
    По максимому отделить вёрстку от кода, желательно использовав какой-нибудь шаблонизатор вместо native-php.
    Тогда формированию html-кода на основании входных данных будет соответствовать простейшая логика, которую и тестировать особенно не нужно.
    Тестировать уже именно формирование этих самых входных данных.
  • Можно считать md5 суммы всей страницы, или отдельных ее частей, сравнивая с эталоном.
Написать ответ

Войдите, чтобы написать ответ

Войти через TM ID
Похожие вопросы