Как ускорить сквозные тесты — три простых правила

“Медленные и ненадежные тесты — бич QA-отдела, они препятствуют внедрению передовых практик, например Trunk-Based-Development и непрерывной интеграции. Во многих современных проектах на выполнение тестовых наборов уходит более 30 минут, а случайные сбои требуют перезапуска набора, замедляя и без того медленное выполнение.

В своей предыдущей статье я поделился стратегией, позволяющей “продвинуть” большинство тестов вниз по пирамиде, с минимальным влиянием на качество и покрытие. Однако вам 100% будут нужны и сквозные тесты, потому что они необходимы для проверки того, что система действительно работает, когда это наиболее важно: когда с ней взаимодействуют пользователи. Проблема в том, как сделать так, чтобы эти сквозные тесты не превращали CI-конвейеры в просто «I» (не непрерывные).

В этой статье я поделюсь своими 3 правилами написания “молниеносных” сквозных тестов, а также реальным бенчмарком, показывающим, насколько быстрыми они могут быть.

Две оси E2E

Первый шаг к предотвращению замедления и хрупкости — это признание того, что существует два разных взгляда на то, что означает термин «end-to-end»:

  • Пользовательский e2e-поток: Верифицирует поток пользователя. В электронном магазине, например, это вход в систему, поиск товара, добавление его в корзину, оформление заказа и получение подтверждения.
  • Системный e2e-поток: Верифицирует одно поведение, проходя всю систему (без моков). Например, один тест будет проверять, что логин работает, другой — функциональность поиска и т. д., и каждый из этих тестов будет использовать реального провайдера аутентификации, реальную базу данных и реальный пользовательский интерфейс — точно так же, как это делал бы реальный пользователь.

Хотя пользовательские E2E-тесты имеют свое законное место в стратегии тестирования, они по своей природе медленные и хрупкие. А вот системные E2E-тесты могут быть удивительно быстрыми. Именно поэтому мое первое правило гласит:

Правило №1: В своем CI-пайплайне фокусируйтесь на системном E2E — а не пользовательском.

Когда мы говорим о тестах нижнего уровня, мы, естественно, фокусируемся на Системной Оси: в юнит-тесте мы тестируем один компонент системы (отдельную функцию или класс) за один раз, а остальные полностью мокируем. В интеграционных тестах мы проверяем одну функциональность за раз, обрабатываемую несколькими компонентами системы вместе (например, прямой вызов конечной точки API с подключенной базой данных).

В обоих случаях мы проверяем один сценарий за раз — один вызов функции или один вызов API — а не поток. Мы различаем Unit и Integration на Системной Оси, а не на Пользовательской.

Но при переходе к E2E-части пирамиды мы часто скользим вперед по Системной Оси , но при этом внезапно совершаем неявный прыжок по Пользовательской в то же самое время, написав тесты, которые проверяют не только всю систему, но и весь пользовательский поток.

Обратите внимание, что я не призываю полностью отказаться от пользовательских E2E-тестов — и позже я расскажу о том, какое место они должны занимать в вашей стратегии.

Два типа сквозных тестов. Красная стрелка обозначает «прыжок»: переход к E2E-тестам, которые проверяют потоки целиком, пропуская E2E-тесты, которые проверяют одно поведение за раз, как это делают юнит- и интеграционные тесты.

Мышление по принципу Given-When-Then

Правило № 1 устанавливает, что тесты системные E2E-тесты проверяют по одной функции за раз. Для этого каждый тест должен иметь «эталонное  состояние» системы. На примере сайта электронной коммерции у нас будет один тест для входа в систему, другой — для поиска товара, третий — для добавления его в корзину и третий — для оформления заказа. Проблема в том, что большинство этих тестов требуют, чтобы система находилась в определенном состоянии, например, когда пользователь уже вошел в систему. Как добиться этого для каждого теста?

Самый очевидный способ — использовать UI для входа в систему, как мы это делали при тестировании формы входа. Но у этого подхода есть серьезные недостатки:

  1. Очень медленный. Для любых фич, кроме самых простых, практически все время выполнения каждого теста будет уходить на настройку, а не на тестирование.
  2. Хрупкий. Чем больше действий выполняется через пользовательский интерфейс, тем больше возможностей для того, чтобы тест стал хрупким. Время от времени настройка интерфейса будет давать сбои, подрывая доверие к тестовому набору.
  3. Очень избыточный. Тестируя по одной сущности за раз, мы стремимся к состоянию, когда одна сломанная фича означает один сломанный тест — так мы можем легко определить проблемы, когда они возникают. Если, например, большинство наших тестов используют пользовательский интерфейс для входа в систему, то неработающая форма входа приведет к сбою многих не связанных с ней тестов, что замаскирует информацию о том, где находится проблема. Мы хотим, чтобы связь между ошибками и отказами тестов была как можно более ясной.

Атомарные тесты (те, которые проверяют одну вещь за тест) состоят из 3 частей. В терминах Gherkin это:

Given: Настройте систему так, чтобы она находилась в состоянии, необходимом для нашего теста

When: Активируйте систему одним действием (или как можно меньше действий)

Then: Проверьте, соответствует ли новое состояние системы тому, что мы ожидали от действия.

Правило #2: Выполняйте этап «Настройки» («Given») с помощью быстрых программных вызовов.

Ключом к правилу №2 является понимание того, что Given-часть на самом деле не является частью теста — это просто настройка. Мы хотим, чтобы настройка выполнялась как можно быстрее. Для этого мы будем использовать программный доступ к нашей системе (аутентификация, заполнение БД и т. д.), чтобы наши тесты тратили как можно меньше времени на настройку.

Например, для теста оформления заказа нам нужен авторизованный пользователь с товаром в корзине. Это наш Given-этап. Чтобы привести систему в такое состояние, мы сначала выполним программный вызов нашего Auth-провайдера для создания сессии входа с токеном, а затем вызов бэкенда с этим токеном для добавления товара в корзину вошедшего пользователя. Эти вызовы будут на порядки быстрее, чем использование пользовательского интерфейса для достижения тех же результатов.

Затем мы выполняем тест и проверяем его с помощью пользовательского интерфейса, чтобы убедиться, что система находится в предполагаемом новом состоянии (части теста When/Then).

Иллюстрация того, какую часть теста мы выполняем через API, а какую — через UI. Текст теста взят из: https://www.subject7.com/gherkin-behavior-driven-testing-hype-or-not/.

Как может выглядеть системный сквозной тест в Playwright

Параллелизация

Точно так же, как мы стремимся, чтобы наши модульные и интеграционные тесты были изолированы, выполнялись в любом нужном порядке и параллельно, то же самое должно относиться и к нашим системным E2E-тестам. При полностью распараллеленных тестах вы можете легко сократить общее время выполнения, добавив больше worker’ов.

Правило № 3: Сделайте системные E2E-тесты параллельными.

Тест-кейс реального мира

Даже при соблюдении всех вышеперечисленных правил время работы CI в одном из моих проектов (приложение Expo) начало приближаться к 10 минутам на одном Github Runner, что делает работу Trunk-Based и Atomic Commits медленной и неудобной. Поскольку на написание одного коммита у меня уходит около 3-5 минут, а CI работает в два раза дольше, я начал сомневаться в том, стоит ли сразу пушить каждый свой коммит, что стало тревожным сигналом.

К счастью, мы с самого начала ориентировали свои системные E2E-тесты на параллельное выполнение, поэтому переход на собственный хостинг с несколькими worker’ами был быстрым и безболезненным, и значительно уменьшил время выполнения (как приятный бонус, собственный хостинг избавляет от необходимости переустанавливать статические зависимости для каждого запуска, такие как docker, браузеры Playwright и т. д., что еще больше сокращает общее время прохода через CI).

Вот бенчмарк Playwright, выполняющий 80 тестов системных E2E-тестов:

Github Runner:

1 worker: Всего: 8:30 минут, Тесты: 5:50 минут.

Собственный раннер:

1 worker: Всего: 6:53, Тесты: 5 минут.

2 worker’а: Всего: 4:12, Тесты: 3 минуты.

3 worker’а: Всего: 3:22, Тесты: 2 минуты.

4 worker’а: Всего: 2:30, тесты: 1,4 минуты.

(Примечание: при использовании 4 worker’а тесты выполнялись настолько быстро, что Auth-провайдер иногда отклонял вызовы из-за ограничения скорости, что приводило к периодическому увеличению времени выполнения, поэтому мы остановились на 3 worker’ах. Устранение одного узкого места может привести к появлению другого, часто неожиданного, поэтому бенчмаркинг очень важен).

Бенчмарк 80 E2E-тестов с 1-3 worker’ами

Чем быстрее работает CI, тем более детализированными могут быть ваши действия, позволяя выявлять ошибки и проблемы интеграции до того, как они успеют накопиться.

Смягчение привязки к имплементации

Один из минусов следования этим правилам заключается в том, что мы отказываемся от важного принципа BDD — что наши тесты не срабатывают только при изменении поведения, а не при изменении деталей реализации. Делая прямые вызовы к нашим API, мы подвергаем наши тесты риску сбоя при модификации или изменении этих API, что не является идеальным. По сути, чтобы ускорить этап «Given«, мы привязываем его к архитектуре.

У каждого подхода есть плюсы и минусы, и если мы получаем молниеносно быстрые тесты, я бы назвал это справедливым компромиссом. Однако мы можем смягчить этот недостаток, добавив слой абстракции между тестами и архитектурой — например, для действий аутентификации есть класс-хелпер, который взаимодействует с провайдером аутентификации и вызывается тестами. Если мы изменим провайдера аутентификации, у нас будет только одно место для исправления, вместо того чтобы проходить через все тесты и исправлять их по одному.

Что делать с пользовательскими E2E-тестами

Чтобы убедиться в том, что наше приложение обеспечивает хороший пользовательский опыт, нам по-прежнему нужны тесты, которые выполняют более длительный поток. К сожалению, такая проверка медленная и хрупкая по своей природе. Поэтому я бы избегал включать пользовательские E2E-тесты в обычный CI-пайплайн.

Вместо этого рассмотрите следующие варианты:

  1. Каждый день: Планируйте эти тесты раз в день, желательно с достаточным количеством повторных попыток, чтобы свести к минимуму влияние флуктуаций, связанных с UI.
  2. Перед развертыванием: Запускайте их перед развертыванием.
  3. После развертывания: Если вы практикуете непрерывное развертывание (CD), рассмотрите возможность запускать их после развертывания, с автоматическим откатом в случае неудачи.

Бонусное правило: Не включайте в CI-пайплайн медленные по своей природе пользовательские E2E-тесты.

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

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

Резюме

Скорость, надежность и покрытие при тестировании напрямую влияют на скорость разработки и качество ПО. К сожалению, E2E-тесты часто становятся проблемной частью конвейера. Но если следовать простым принципам, знакомым по модульным и интеграционным тестам, можно сделать свои E2E-тесты удивительно быстрыми и стабильными. Итак, еще раз:

  1. Сфокусируйте свой CI-конвейер на системных E2E-тестах, которые проверяют по одной фиче за раз.
  2. Ускорьте их настройку с помощью programmatic-вызовов.
  3. Выполняйте их параллельно.
  4. Бонусное правило: Переведите более длинный поток пользовательских E2E-тестов в pre/post-deployment запуски.

Следуя этим правилам, вы сохраните и скорость, и качество, гарантируя, что E2E-тестирование будет поддерживать ваш процесс разработки, а не мешать ему.”

Gitconnected


Какой была ваша первая зарплата в QA и как вы искали первую работу?

Мега обсуждение в нашем телеграм-канале о поиске первой работы. Обмен опытом и мнения.

Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии

Мы в Telegram

Наш официальный канал
Полезные материалы и тесты
Готовимся к собеседованию
Project- и Product-менеджмент

? Популярное

? Telegram-обсуждения

Наши подписчики обсуждают, как искали первую работу в QA. Некоторые ищут ее прямо сейчас.
Наши подписчики рассказывают о том, как не бояться задавать тупые вопросы и чувствовать себя уверенно в новой команде.
Обсуждаем, куда лучше податься - в менеджмент или по технической ветке?
Говорим о конфликтных ситуациях в команде и о том, как их избежать
$1100*
медианная зарплата в QA в июне 2023

*по результатам опроса QA-инженеров в нашем телеграм-канале

Собеседование

19%*
IT-специалистов переехало или приняло решение о переезде из России по состоянию на конец марта 2022

*по результатам опроса в нашем телеграм-канале

live

Обсуждают сейчас