automationciflakypipelinedevopsqa

Автотесты, которые не в CI, — это хобби: как встроить тесты в пайплайн и не утонуть

Видел не одну команду с приличным набором автотестов, которые гоняются… иногда. Локально, у автоматизатора, «перед большим релизом». Скажу жёстко, как для себя когда-то сформулировал: автотест, который не гоняется в CI на каждое изменение, — это не автоматизация, это хобби. Ценность автотеста — в раннем и надёжном сигнале «сломалось», а сигналить он умеет только из пайплайна. Всё остальное — код, который стареет.

При этом «просто включить тесты в CI» — отличный способ утонуть: прогоны по часу, флаки валят каждый второй билд, разработчики жмут retry не глядя, через месяц красный пайплайн — фон, на который никто не смотрит. Разберу, из чего состоит встройка, которая работает.

Слои: не всё гоняется на каждый чих

Первая ошибка — гонять всё на каждый коммит. Полный e2e-регресс на каждый PR — это часовой фидбек, а смысл continuous integration ровно в обратном: быстро узнавать, что изменение ничего не сломало. Медленный гейт люди перестают ждать — и начинают обходить.

Я раскладываю прогоны на три слоя. На каждый PR — быстрое: юниты, компонентные, API-тесты, смоук критичных путей; бюджет — минуты, не десятки минут. Если PR-гейт дольше ~10 минут — режем состав, а не терпим. После merge в main — основной регресс: то, что слишком долго для PR, но должно ловиться до того, как поверх наслоятся новые изменения. Ночью/по расписанию — тяжёлое и медленное: полный e2e, кросс-браузер/девайсы, нагрузочное, визуальная регрессия по всем страницам.

Критерий распределения простой: чем раньше слой, тем быстрее и стабильнее должны быть его тесты. Медленный или подфлакивающий тест не «выкидывается» — он переезжает слоем позже.

Параллелизм: тесты валят друг друга, а виноват CI

Единственный способ держать большой suite быстрым — параллель и шардирование: Playwright умеет --shard из коробки (и вообще хорошо документирован для CI), в pytest есть pytest-xdist. Но параллель безжалостно вскрывает грех, про который я писал в посте про тестовые данные: зависимые тесты и общие данные. Два шарда одновременно редактируют одного юзера — и падают оба, по-разному, только под нагрузкой CI и никогда локально. Выглядит как «CI глючит», является «тесты зависимы».

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

Retry: страховка, которая легко превращается в наркотик

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

Моё правило: retry максимум 1–2, и каждый passed-on-retry фиксируется — в отчёте, в метрике, в списке флаки-кандидатов. Прошёл со второго раза — это не зелёный, это жёлтый: тест попадает под подозрение, и у «жёлтого» есть владелец и срок разбора. В посте «что автоматизировать» я писал, что флаки-тест хуже отсутствующего — retry без учёта это ровно способ разрешить флаки жить вечно.

Карантин: флаки не мьютим, а изолируем

Что делать с тестом, который мигает, прямо сейчас, пока не починили? Худший вариант — закомментировать/замьютить: дыра в покрытии становится невидимой и вечной. Лучший из рабочих — карантин, как это описывал ещё Google Testing Blog в разборе флаки-тестов: тест продолжает гоняться, но его результат не влияет на статус билда, а сам факт карантина — это тикет с владельцем и дедлайном.

Ключевое — из карантина ровно два выхода: тест починен и вернулся в строй, или тест признан не стоящим поддержки и удалён. Карантин без дедлайна — это то же кладбище, только с красивым названием. Полезный лимит: карантин не больше N тестов одновременно (скажем, 10) — переполнился, значит, у suite системная проблема, и она важнее новых фич.

Красный main: правило, без которого всё остальное бессмысленно

Технически можно собрать идеальный пайплайн и всё равно прийти к «красное — это нормально, оно всегда такое». Спасает только социальный контракт: красный main чинится немедленно или изменение откатывается. Не «посмотрю после обеда» — немедленно, потому что поверх красного мержат новые изменения, и через день уже непонятно, кто виноват. Хорошо работает дежурство: билд-шериф недели, который триажит падения — не чинит всё сам, а гарантирует, что у каждого красного есть владелец. И жёсткое правило: нельзя мержить в красный main «а у меня-то тесты прошли».

Тут важна честность в обе стороны: если тест упал из-за инфраструктуры (стенд лёг, docker registry недоступен) — это тоже сигнал, только про надёжность пайплайна, и у него тоже должен быть владелец. «Инфра-флаки» разъедает доверие так же, как тестовый.

Падение без артефактов = дебаг заново

Тест упал в CI, а дальше — «не воспроизводится локально», и автоматизатор полдня гоняет его руками. Лечится артефактами: на падении сохраняются скриншот, видео, трейс (в Playwright — trace: 'on-first-retry', открывается в trace viewer со всеми действиями, снапшотами DOM и сетью), логи приложения за время теста. Артефакты цепляются к прогону в CI, отчёт с ними приходит прямо в PR. Хорошее падение читается без перезапуска: что делал тест, что увидел, что пришло из API.

Плюс история: единичный отчёт говорит «что упало сейчас», а история прогонов (Allure, ReportPortal, встроенные дашборды CI) — «что падает чаще всех» и «когда suite начал деградировать». Топ-10 флаки за месяц — самый честный бэклог автоматизатора.

Окружение: не гоняйте CI-тесты на общем staging

Тесты в CI, которые ходят на общий staging, где параллельно тыкают люди и крутятся другие пайплайны, — источник «загадочных» падений: данные меняются под ногами, версии кода на стенде и в PR разъезжаются. Идеал — эфемерное окружение на прогон: docker compose / Testcontainers поднимают приложение с зависимостями внутри пайплайна, прогнали — снесли. Не тянет проект — хотя бы выделенный стенд только для автотестов, куда деплоится ровно проверяемая версия.

Метрики здоровья suite

Чтобы деградацию видеть заранее, а не «что-то CI совсем плохой стал», достаточно четырёх цифр на дашборде: длительность прогона (тренд), flake rate (% passed-on-retry), time-to-green (сколько красный main живёт до починки), размер карантина. Растёт любая — это управляемый сигнал, а не ощущение.

Чек-лист «тесты в CI»

  • Слои: быстрый PR-гейт (минуты) / после merge / ночной тяжёлый
  • PR-гейт дольше ~10 минут — пересматриваем состав, а не терпим
  • Тесты проходят в случайном порядке с уникальными данными — до включения параллели
  • Retry 1–2 максимум, passed-on-retry фиксируется как флаки-кандидат
  • Карантин: гоняется, не влияет на статус, тикет с дедлайном, лимит размера
  • Красный main: чинится немедленно или откат; дежурный-шериф; поверх красного не мержим
  • Артефакты на падении: скриншот, видео, trace, логи — падение читается без перезапуска
  • История прогонов и топ флаки — виден тренд, не только текущий статус
  • Окружение эфемерное или выделенное, не общий staging
  • Дашборд: длительность, flake rate, time-to-green, размер карантина

Пайплайн с тестами — это продукт со своими пользователями: разработчиками, которые ждут его вердикта по десять раз на дню. У продукта есть UX (скорость фидбека), надёжность (флаки) и поддержка (дежурство, карантин). Относиться к нему хуже, чем к продукту для внешних юзеров, — значит удивляться, почему им перестали пользоваться.