Просто скажите “нет” новым end-to-end тестам

Автор: Майк Уокер (Mike Wacker)

Дата: April 22, 2015

Оригинал: https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html

Наверняка в вашей жизни был момент, когда вы с друзьями очень хотели посмотреть какой-то фильм, а после просмотра все одинаково сильно разочаровались. Или, возможно, вы вспомните, как ваша команда считала, что нашла следующую “killer feature” для своего продукта, — лишь для того, чтобы та провалилась после релиза.

Хорошие идеи часто терпят неудачу на практике. В мире тестирования одна такая распространённая и, казалось бы, хорошая идея, которая часто не оправдывает себя, — это стратегия тестирования, построенная вокруг сквозных тестов (end-to-end tests).

Тестировщики могут вкладывать своё время в написание разных видов автоматизированных тестов, включая модульные (unit), интеграционные (integration) и сквозные (end-to-end) тесты. Но упомянутая стратегия подразумевает основную ставку на сквозные тесты, которые проверяют продукт или сервис в целом. Как правило, такие тесты имитируют сценарии реального пользователя.

Сквозные тесты в теории

Хотя делать основную ставку на сквозные тесты — это плохая идея, нельзя не признать, что в теории она кажется вполне разумной.

Для начала: пункт номер один в знаменитом списке Google «десять золотых принципов» гласит: «Нужно сосредоточиться на пользователе, а всё остальное приложится». Таким образом, сквозные тесты, которые фокусируются на реальных пользовательских сценариях, звучат как отличное решение. Кроме того, такая стратегия в целом привлекательна для многих сторон:

  • Разработчикам она нравится, потому что позволяет переложить большую часть, если не всю, ответственность за тестирование на других.
  • Менеджерам и руководителям она нравится, потому что тесты, имитирующие действия реальных пользователей, позволяют им легко оценить, как повлияет на пользователя тот или иной проваленный тест.
  • Тестировщикам она нравится, потому что они часто боятся пропустить баг или написать тест, который не проверяет реальное поведение системы; написание тестов с точки зрения пользователя часто позволяет избежать обеих проблем и даёт тестировщику большее чувство удовлетворения от работы.

Сквозные тесты на практике

Итак, если эта стратегия тестирования так хороша в теории, то где же она даёт сбой на практике? Чтобы это продемонстрировать, я опишу следующий собирательный образ, основанный на целом ряде реальных ситуаций, знакомых и мне, и другим тестировщикам. В этом примере команда разрабатывает сервис для онлайн-редактирования документов (например, такой как Google Docs).

Предположим, у команды уже развёрнута прекрасная тестовая инфраструктура. Каждую ночь:

  • Собирается последняя версия сервиса.
  • Эта версия развёртывается в тестовом окружении команды.
  • Затем все сквозные тесты запускаются против этого тестового окружения.
  • Команде отправляется email-отчёт с сводкой результатов тестирования.

Срок сдачи неумолимо приближается, а наша команда всё пишет код для новых функций своего следующего релиза. Чтобы сохранить высокую планку качества продукта, они также требуют, чтобы как минимум 90% их сквозных тестов проходили успешно, прежде чем функции будут считаться готовыми. На данный момент до дедлайна остаётся всего один день:

Дни до релиза% прохожденияПримечания
15%Все сломалось! Вход в сервис не работает. Почти все тесты выполняют вход пользователя, поэтому почти все тесты провалились.
04%Команда-партнёр, от которой мы зависим, вчера развернула баговую сборку в своём тестовом окружении.
-154%Разработчик вчера (или позавчера?) сломал сценарий сохранения. Половина тестов в какой-то момент сохраняет документ. Разработчики потратили большую часть дня на определение, это баг фронтенда или бэкенда.
-254%Это баг фронтенда, разработчики потратили половину сегодняшнего дня, выясняя где именно.
-354%Вчера был внесён неправильный фикс. Однако ошибку было довольно легко обнаружить, и сегодня был внесён корректный фикс.
-41%В лаборатории произошли аппаратные сбои нашего тестового окружения.
-584%Множество мелких багов прятались за крупными (например, сломанный вход, сломанное сохранение). Всё ещё работаем над мелкими багами.
-687%Мы должны быть выше 90%, но по какой-то причине это не так.
-789.54%(Округляется до 90%, достаточно близко.) Вчера не было внесено никаких исправлений, значит тесты вчера были нестабильными.

Анализ

Несмотря на множество проблем, тесты в конечном счёте действительно выявили реальные баги.

Что сработало:

  • Критические ошибки, затрагивающие пользователей, были найдены и исправлены до того, как достигли клиента.

Что пошло не так:

  • Команда завершила майлстоун по разработке с опозданием на неделю (и при этом много работала сверхурочно).
  • Поиск первопричины падения сквозного теста — это болезненный и долгий процесс.
  • Сбои в работе смежных систем и проблемы в тестовой среде неоднократно искажали результаты тестирования.
  • Многие мелкие баги были скрыты за более крупными.
  • Сквозные тесты временами были ненадёжными (flaky).
  • Разработчикам приходилось ждать до следующего дня, чтобы узнать, сработало ли их исправление.

Итак, теперь, когда мы понимаем, что не так со стратегией, основанной на сквозных тестах, нам нужно изменить наш подход к тестированию, чтобы избежать многих из этих проблем. Но какой же подход является правильным?

Истинная ценность тестов

Обычно работа тестировщика заканчивается, как только тест падает. На баг заводится тикет, и его устранение становится задачей разработчика. Однако, чтобы понять, в чём именно стратегия сквозного тестирования даёт сбой, нам нужно выйти за эти рамки и подойти к проблеме с позиции первопричин. Если мы действительно «сосредотачиваемся на пользователе (а всё остальное приложится)», мы должны спросить себя: какую пользу пользователю приносит упавший тест? И вот ответ:

Упавший тест не приносит пользователю прямой пользы.

Хотя это утверждение поначалу шокирует, оно верно. Если продукт работает, он работает — независимо от того, что говорит тест. Если продукт сломан, он сломан — даже если тест этого не показывает.
Итак, если упавшие тесты не приносят пользы пользователю, тогда что же её приносит?

Пользу пользователю приносит исправление бага.

Пользователь будет доволен только тогда, когда некорректное поведение — баг — исчезнет. Очевидно, чтобы исправить баг, вы должны знать о его существовании. Чтобы знать о его существовании, в идеале у вас должен быть тест, который этот баг отлавливает (потому что если тест этого не сделает, баг найдёт пользователь). Но во всём этом процессе — от упавшего теста до исправления бага — ценность создаётся только на самом последнем шаге.

Таким образом, для оценки любой стратегии тестирования недостаточно анализировать лишь то, как она находит ошибки. Необходимо также оценивать то, как она помогает разработчикам исправлять (и даже предотвращать) эти ошибки.

Создание правильной петли обратной связи

Тесты создают петлю обратной связи, которая сообщает разработчику, работает продукт или нет. Идеальная петля обратной связи обладает несколькими свойствами:

  • Она быстрая. Ни один разработчик не хочет ждать часами или днями, чтобы узнать, работает ли его изменение. Иногда изменение не работает — никто не идеален — и петля обратной связи должна запускаться многократно. Более быстрая обратная связь приводит к ускоренному исправлению ошибок. Если петля достаточно быстрая, разработчики могут даже запускать тесты до фиксации изменений.
  • Она надёжная. Ни один разработчик не хочет тратить часы на отладку теста, только чтобы обнаружить, что он был ненадёжным (flaky). Ненадёжные тесты подрывают доверие разработчика к тестированию, и в результате их часто начинают игнорировать, даже когда они обнаруживают реальные проблемы в продукте.
  • Она локализует проблему. Чтобы исправить ошибку, разработчикам необходимо найти конкретные строки кода, вызывающие её. Когда продукт содержит миллионы строк кода, а ошибка может быть где угодно, это похоже на поиск иголки в стоге сена.

Мыслите меньшими масштабами

Итак, как же создать эту идеальную петлю обратной связи? Мыслите меньшими, а не большими масштабами.

Модульные тесты (Unit Tests)

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

  1. Модульные тесты — быстрые. Нам нужно собрать для теста лишь небольшой модуль, и сами тесты тоже обычно весьма компактны. Фактически, десятая доля секунды уже считается медленной для модульного теста.
  2. Модульные тесты — надёжные. Простые системы и небольшие модули в целом гораздо менее подвержены ненадёжности. Более того, лучшие практики модульного тестирования — в частности, подходы, связанные с герметичными тестами (hermetic tests) — позволяют устранить ненадёжность полностью.
  3. Модульные тесты изолируют сбои. Даже если продукт содержит миллионы строк кода, при падении модульного теста вам нужно искать ошибку только в рамках того небольшого модуля, который тестируется.

Написание эффективных модульных тестов требует навыков в таких областях, как управление зависимостями, использование моков (mock objects) и герметичное тестирование. Я не буду здесь раскрывать эти темы, но в качестве стартового примера, который обычно показывают новым сотрудникам Google (или “нуглерам”), можно привести то, как в Google создают и тестируют секундомер.

Модульные тесты против сквозных тестов

Со сквозными тестами вам приходится ждать: сначала полной сборки продукта, затем его развертывания и, наконец, выполнения всех сквозных тестов. Когда тесты всё же запускаются, ненадёжные результаты становятся суровой реальностью. И даже если тест находит баг, этот баг может находиться где угодно в продукте.

Хотя сквозные тесты и лучше имитируют реальные пользовательские сценарии, это преимущество быстро перевешивается всеми недостатками сквозной петли обратной связи:

Интеграционные тесты

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

Если два модуля интегрируются неправильно, зачем писать сквозной тест, если можно создать гораздо более компактный и сфокусированный интеграционный тест, который обнаружит ту же ошибку? Вам, конечно, нужно мыслить шире, но лишь немного шире, чтобы проверить взаимодействие модулей.

Пирамида тестирования

Даже при наличии и модульных, и интеграционных тестов вам, вероятно, всё равно потребуется небольшое количество сквозных тестов для проверки системы в целом. Чтобы найти правильный баланс между всеми тремя типами тестов, лучшим наглядным пособием служит пирамида тестирования. Вот её упрощённая версия из вступительного доклада на Конференции по автоматизации тестирования Google (GTAC) 2014 года:

Основную массу ваших тестов составляют модульные тесты, расположенные в основании пирамиды. По мере движения вверх по пирамиде тесты становятся крупнее, но при этом их количество (ширина пирамиды) — уменьшается.

В качестве хорошего стартового ориентира Google часто предлагает распределение 70/20/10: 70% модульных тестов, 20% интеграционных и 10% сквозных. Идеальное соотношение будет своим для каждой команды, но в целом оно должно сохранять форму пирамиды. Старайтесь избегать этих антипаттернов:

  • Перевёрнутая пирамида / Рожок мороженого: Команда в основном полагается на сквозные тесты, используя мало интеграционных и ещё меньше модульных.
  • Песочные часы: Команда начинает с большого количества модульных тестов, но затем использует сквозные тесты там, где должны быть интеграционные. В результате получается много модульных тестов внизу и много сквозных вверху, но очень мало интеграционных посередине.

Подобно тому, как обычная пирамида является самой устойчивой структурой в реальном мире, пирамида тестирования — это самая стабильная и надёжная тестовая стратегия.