Доклад “BDD. Яйцо или курица. Тест или код?”
В июне выступил с докладом на внутренней конференции управления разработки — КонфУР. Рассказал историю развития автотестов в нашей команде и немного о технологиях, которые используем. Здесь представлю краткую выжимку из доклада.
Команда, в которой я работаю, занимается заказной разработкой как для внутренних, так и для внешних заказчиков. Пишем на Ruby on Rails, для приёмочных тестов используем связку Cucumber + Capybara + PhantomJS. В остальных тестах используется Rspec.
Cucumber — отличное средство для создания сценариев приёмочных тестов. Если кто-то не знаком с ним, то вот так выглядит пример теста:
Background: Given I logged in as "Операторов Аркадий" And I am administrator And following user exist | last_name | first_name | | Тестовый | Иван | And I open certificate bind page
Scenario: when user does not have certificate When I bind certificate to user "Тестовый Иван" Then I should see text "Сертификат привязан пользователю"
Мы разработали несколько продуктов, которые в текущее время поддерживаем.



Изначальный процесс
На этапе формирования команды все тесты, в частности end-to-end тесты, писали разработчики.

У такого процесса было несколько жирных минусов:
- 30-40% времени разработчика уходило на тесты;
- сценарии писались параллельно с юнит-тестами, из-за чего получались излишне детализированными и длинными;
- сценарии, написанные разработчиками, совпадали с тест-кейсами тестировщика для ручного тестирования — одна и та же работа выполнялась два раза.
Мы не могли позволить себе тратить столько времени разработчиков на написание тестов и начали думать, как ускорить процесс разработки. Переложить ответственность за приёмочные тесты на тестировщика — самое очевидное и простое решение. Проблема была лишь в том, что я ни разу до этого не сталкивался с ruby и cucumber. Тем не менее, мы решили попробовать схему, когда за end-to-end тесты отвечает тестировщик, а разработчики прекращают их писать. Договорились, что свободное от тестирования задач время я буду тратить на автотесты, выкладывать их отдельным отдельным пулл-реквестом, а разработчики будут проводить ревью.
Приёмочные тесты пишет тестировщик

Спустя некоторое время проявились минусы такого подхода:
- потеря контекста разработчиками и тестировщиком — тесты на задачу зачастую создавались уже после того, как она была в основной ветке кода, и часто уже была доступна пользователям;
- тесты остались длинными и сложными, потому что я писал их, придерживаясь устоявшихся приципов;
- разработчик может вообще не запускать приложение и не знать, как оно работает со стороны пользователя, ведь он больше не пишет функциональные тесты.
Но были и плюсы: мы разгрузили разработчиков, а я въехал в автотесты.
Обсудив однажды наш процесс разработки, мы воодушевились идеей, что писать приёмочные тесты параллельно разработке задачи — круто и полезно. И решили постараться сделать процесс таким, чтобы в тестирование задача попадала с уже прошедшими приёмочными тестами. Кроме того, мы решили, что в таких тестах должны остаться только проверки работоспособности бизнес-функциональности приложения. Не надо в приёмочных тестах проверять, виден ли элемент на странице, какое значение находится в инпуте, какого цвета кнопка или отображение всех возможных ошибок на форме. Это должны делать более низкоуровневые тесты.
Такое решение позволило сделать наши тесты понятными, короткими и лёгкими в поддержке.
Cucumber удобен тем, что сами сценарии можно писать во время создания тест-плана по задаче. Создаёшь интеллект-карту, выделяешь в ней сценарии, которые нужно автоматизировать, и параллельно в блокноте пишешь эти сценарии на Gherkin.

После этого пушишь сценарии в отдельную ветку и говоришь разработчику: “Смотри, тесты для твоей задачи вот в этой ветке”.
Возникает вопрос, а как же писать вспомогательный код для шагов сценария, если не знаешь, какой id кнопки пропишет разработчик? Ответ прост — пишешь вспомогательный код, а внутри добавляешь пометку для разработчика с текстом. Ему останется лишь немного подпилить тест.
Then(/^I should see "([^"]+?)“ input is invalid$/) do | input | #FIXME добавить тестовый класс лайтбокса within(‘#FIXME’) do step %(I should see “.cc-#{input}” selector input is invalid) endend
Начали писать тесты параллельно с разработкой

Это привело к тому, что у нас:
- появился минимально-необходимый набор тестов для проверки работоспособности функциональности, не запуская приложение;
- новый тестировщик/разработчик сможет легко разобраться в основной функциональности;
- тесты легче изменяются;
- после каждой задачи есть пачка регрессионных тестов.
Общие степы
У cucumber удобная система определения шагов сценариев (step definitions). Спустя какое-то время мы заметили, что у наших продуктов есть общие степы, которые мы копируем из одного проекта в другой. Решено было вынести общие степы в шаблонный проект. Из него мы разворачиваем приложение с нуля при разработке нового сервиса. Получилось, что у тестировщика появился большой набор степов, используя которые можно не заглядывать во вспомогательный код степа и не думать, как же это работает.
Более того, во степах сразу прописаны локаторы общих элементов, использующихся в вёрстке наших продуктов. Это позволило писать тесты на общие компоненты, просто выбирая нужные степы, не заглядывая в вёрстку.

В дальнейшем это и вовсе позволило нам создать шаблоны тестов для общей функциональности. Например, во многих продуктах, которые мы делаем, существует фильтрация организаций по ИНН. Шаблон теста для фильтра выглядит так:
Scenario: user filters <something> by inn# Given following organizations exists# | short_name | inn |# | Ромашка | 7845881001 |# | Гладиолус | 7845881002 |# | Промакашка | 784588100300 |When a request is made to ""Then I should see 3 table rowsWhen apply <something> filter "7845881001"Then I should see 1 table rowAnd I should see text "Ромашка" in "<column-name>" column of "1" table rowWhen I apply <something> filter "7845881003"Then I should not see table rowsAnd I should see text "7845881003" in <something> filterWhen I reset the filterThen I should see 3 table rows
Нужно лишь засетапить необходимые тестовые данные. Скорость создания тестов для таких типичных задач увеличилась кратно.
Гайд по тестированию
В wiki создали гайд по тестированию, в котором указывается, что тестируют разные виды тестов, и какие инструменты необходимо использовать.

Тесты на русском
Да :)
Решили поэкспериментировать. Взяли небольшой сервис для внутреннего заказчика и попрбовали написать сценарии приёмочных тестов на русском языке. Изначально казалось, что получится ересь, но в итоге оказалось неплохо:
- переход на родной язык позволяет не отвлекаться на перевод при чтении сценариев, появляется возможность сосредоточиться на содержимом;
- открылась проблема плавающей терминологии между разработчиками/тестировщиком/аналитиком;
- заметили излишнее описание взаимодействий с интерфейсом, чего не было заметно на английском языке;
- впервые получилось призвать аналитика к согласованию сценариев.
Выводы из доклада
- Экспериментируйте. Пробуйте новое
- Не бойтесь уменьшить количество тестов
- Обсуждайте улучшения
- Попробуйте cucumber или его аналоги
Пара интересных книжек:
