Найти в Дзене

Почему не нужно писать тесты на визуал при разработке

Вопрос касается не только TDD-разработки, но и написания качественных unit-тестов в целом. В прошлой заметке был тест на проверку отправки юзеру в бот ссылки для входа на сайт. Вот код (я еще про Argument::that() хотел отдельно написать): // ожидаем что будет вызван метод sendMessage() где в тексте сообщения будет Please click the button below...
// а ссылка в кнопке Login будет содержать роут быстрого логина
$this->api->sendMessage(Argument::that(function ($params) use ($user) {
return $params['text'] === "Please click the button below to log in with your account. You will be automatically redirected to your dashboard." &&
strpos($params['reply_markup']['inline_keyboard'][0][0]['url'], route('bot.quick-login', ['user' => $user->id])) === 0;
}))->shouldHaveBeenCalled();
Здесь мы проверяем, что пользователю в конце ушёл в виде сообщения
определённый текст. Каковой текст и является визуалом, то есть представлением. Это вывод текста и кнопки-ссылки

Вопрос касается не только TDD-разработки, но и написания качественных unit-тестов в целом.

В прошлой заметке был тест на проверку отправки юзеру в бот ссылки для входа на сайт. Вот код (я еще про Argument::that() хотел отдельно написать):

// ожидаем что будет вызван метод sendMessage() где в тексте сообщения будет Please click the button below...
// а ссылка в кнопке Login будет содержать роут быстрого логина
$this->api->sendMessage(Argument::that(function ($params) use ($user) {
return $params['text'] === "Please click the button below to log in with your account. You will be automatically redirected to your dashboard." &&
strpos($params['reply_markup']['inline_keyboard'][0][0]['url'], route('bot.quick-login', ['user' => $user->id])) === 0;
}))->shouldHaveBeenCalled();


Здесь мы проверяем, что пользователю в конце ушёл в виде сообщения
определённый текст. Каковой текст и является визуалом, то есть
представлением. Это вывод текста и кнопки-ссылки в форме телеграм-сообщения. И этот текст тестировать не нужно.Почему? Потому что текст (представление) не является функциональной частью нашего решения. Мы можем написать какое угодно другое сообщение, но пока в нём содержится ссылка на логин, наша фича работает корректно. То есть конкретное содержимое текста совершенно не имеет значения.

Почему ещё? Потому что в ходе развития проекта мы текст можем много раз улучшить и переписать. Что повлечёт необходимость менять соответствующие тесты. Безо всякой на то нужды, поскольку кнопку и роут входа мы не меняли.

Иными словами, при написании тестов, будь то TDD, или просто тесты из общих соображений, мы тоже должны отделять бизнес-логику от представления. Тест — это не застывшее описание реализации, а спецификация поведения программы. А смена текста сообщения (представления) никак не меняет поведение (бизнес-логику). Поэтому тест не должен ломаться при смене текста.

Текст был бы уместен, если бы кроме него никаких проверяемых результатов у нашей фичи бы не было. И то, тогда лучше проверить не наличие конкретных букв, а название строки из массива локализаций. Тогда проверку в упомянутом тесте можно было бы переписать так:

$this->api->sendMessage(Argument::that(function ($params) use ($user) {
return $params['text'] === __('login_link.description') &&
strpos($params['reply_markup']['inline_keyboard'][0][0]['url'], route('bot.quick-login', ['user' => $user->id])) === 0;
}))->shouldHaveBeenCalled();


Возвращаясь к Argument::that() — как вы уже могли заметить, этот статичный метод (из мок-библиотеки Prophecy) позволяет проверять в переданных аргументах только то, что действительно нужно проверить. А без него методы типа shouldHaveBeenCalled() будут проверять все аргументы на точное совпадение. Что сделает наши тесты очень деревянными.

Пример я взял из телеграм-бота просто потому что он был под рукой. Но абсолютно то же самое применимо и к веб-страницам. Не нужно, 10 раз не подумав, загонять в тесты конкретную верстку, или конкретные тексты. Нашим тестам от этого только вред: вывести фичу не поможет, а исправлять тесты потом придётся часто, причем безо всякой пользы для кода.

А для тестирования визуала есть более подходящие способы: инструменты, сравнивающие референсные страницы с заранее сохраненными скринами попиксельно и поднимающие шухер, если вдруг что-то куда-то съехало. Ghost Inspector, например.