Навеяно топиком
"«Я не пишу юнит-тесты, потому что ...» — отговорки". Решил ещё раз попробовать использовать TDD.
Имеется некое MVC веб-приложение. Допустим, что модели и контроллеры тестами покрыты. А вот как тестировать вьюхи, шаблоны, лайауты, результатом работы которых является непосредственный вывод на stdin. Понятно, что вывод можно перехватить через ob_*, но вот как тестировать вообще HTML код (native PHP шаблоны)?
Попробовал так:
На главной странице должна быть ссылка на user/new (регистрация). Написал тест (PHPUnit):
function testRegisterLinkForAnonymousPresent()<br>
{<br>
$app = new App();<br>
$this->expectOutputRegex("#<a.+href='/user/new'.*>.+</a>#");<br>
$app->run();<br>
}
Настроил вывод этой ссылки на главной — тест прошёл
Следующий шаг — по /user/new должна выводиться форма регистрации с action /user и методом post. Написал тест:
function testRegisterFormPresentAndValid()<br>
{<br>
$app = new App();<br>
$request = new Request('/user/new');<br>
$app->setRequest($request);<br>
$this->expectOutputRegex("#<form.+action='/user'.+method='POST'.*></form>#");<br>
$app->run();<br>
}
Сделал форму, тест прошёл, но потом обратил внимание, что опечатался в шаблоне
<form action='/user'bmethod='POST'><br>
</form>
Начал думать над регуляркой и понял, что она ошибочна в принципе — атрибуты могут быть в другом порядке, после form обязателен пробел и т. п. начал рисовать двухэтажную, потом она плавно стала перетекать в трёхэтажную. А ведь ещё не дошёл до проверки полей, порядок которых тоже может быть произвольным. Собственно тут начал гуглить как выходят из положения, узнал много нового про TDD, но конкретно по сабжу ничего не нашёл толком.
Подскажите, пожалуйста, куда рыть?
Регулярками, имхо, не реально проверять сложные структуры. Пока два варианта:
— Проверять теги не целиком, а по частям: в одном тесте/ассерте проверить, что есть тег form в принципе, во втором, что в нём есть атрибут action с нужным значением, в третьем, что method есть с POST, в четвёртом, что есть тег input внутри form. Чтобы избегать трёхэтажных выражений можно параллельно с проверкой вырезать нужные куски, а уж потом проверять только их, например "#<form(.*)>(.*)#"
— Парсить HTML и проверять тэги/атрибуты/значения уже в дереве в объектном виде.
P.S. Это не функциональное тестирование, не смотря на то, что вроде бы проверяется всё приложение, просто так совпало, рефакторинг ещё не делал, чтобы проверить шаблон отдельно — проверять нужно именно работу юнита, результат которой HTML-код на наличие нужной комбинации тегов/атрибутов/значений/текста.
Updated:
В процессе ковыряния Зенда нашёл практически недокументированные функции PHPUnit assertSelectCount(), assertSelectEquals, assertSelectRegExp(), теперь тест выглядит так:
function testRegisterLinkForAnonymousPresent()<br>
{<br>
ob_start();<br>
$app = new App();<br>
$app->run();<br>
$result = ob_get_contents();<br>
ob_end_clean();<br><br>
$this->assertSelectEquals('a[href="/user/new"]', 'Register', 1, $result);<br>
}
Наверное надо расширить класс PHPUnit_Extensions_OutputTestCase чтобы избавиться от ob_*