Mid Авторский

Flaky-тесты: проблема и подходы

Почему нестабильные тесты разрушают доверие к CI и как с ними бороться

Первоисточник

Flaky Tests at Google and How We Mitigate Them

John Micco, 2016

Когда CI перестают доверять

Вы нажимаете «re-run» на упавшем CI-пайплайне, тесты проходят, вы мержите PR. Через час коллега делает то же самое — тест упал, она перезапускает, тест проходит, PR мержится. К концу дня команда из восьми человек перезапустила CI суммарно двенадцать раз. Каждый перезапуск занял 15–20 минут ожидания. Но главная потеря — не во времени, а в доверии: когда тесты падают без причины, разработчики перестают воспринимать красный CI как сигнал о реальной проблеме. «Наверное, опять flaky» — и перезапуск вместо расследования. А когда в один прекрасный день тест упадёт из-за настоящего бага, его проигнорируют точно так же.

Это и есть главная опасность flaky-тестов: они разрушают сигнал. CI существует для того, чтобы давать быструю обратную связь о состоянии кода, и flaky-тесты превращают эту обратную связь в шум.

Масштаб проблемы: данные Google

John Micco из Google опубликовал в 2016 году на Google Testing Blog статью, которая впервые дала индустрии количественное представление о масштабе проблемы. Цифры оказались шокирующими: при том, что всего 1.5% прогонов тестов в Google показывали flaky-поведение, это затрагивало около 16% всех тестов. Иными словами, каждый шестой тест в Google хотя бы иногда падал без связи с реальным изменением кода.

Ещё более интересная цифра: 84% переходов из состояния «passed» в «failed» были вызваны flaky-тестами, а не реальными поломками. Это значит, что когда инженер в Google видел красный тест, с вероятностью 84% проблема была не в его коде. Представьте, какое отношение к тестам формирует такая статистика.

Для среднего проекта с 1000 тестов 1.5% flakiness rate означает, что при каждом прогоне примерно 15 тестов могут упасть по причинам, не связанным с изменениями. Если ваш CI блокирует мерж при любом красном тесте, разработчики будут перезапускать пайплайн снова и снова — и тратить часы в день на ожидание.

Почему тесты становятся flaky

Причины flaky-поведения распадаются на несколько категорий, и понимание категории определяет стратегию борьбы.

Зависимость от состояния

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

Зависимость от времени

Тест содержит sleep(2000) или проверяет timestamp, и при большой нагрузке на CI-сервере два секунды оказывается недостаточно. Или тест проверяет «событие произошло не позднее 5 секунд назад», а в медленном контейнере всё сдвигается.

Зависимость от внешних сервисов

Тест ходит в реальный API, и иногда API отвечает медленно или возвращает другой результат. Или тест зависит от DNS-резолвинга, сетевой связности, доступности внешнего Docker registry.

Конкурентность и race conditions

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

Утечки ресурсов

Тест не освобождает соединение с базой, не закрывает файл, не останавливает фоновый процесс — и после сотни тестов система исчерпывает ресурсы.

Стратегии борьбы

Карантин (quarantine)

Google использует автоматический карантин: инструмент мониторит частоту flaky-поведения каждого теста, и если она превышает порог, тест автоматически выводится из критического пути CI и попадает в отдельный «карантинный» набор. Одновременно система создаёт баг для владельца теста с требованием починить его. Тест продолжает бежать, но его результат не блокирует мерж.

Карантин решает проблему доверия к CI: если красный тест в основном наборе — это всегда реальная проблема, разработчики перестают игнорировать красные пайплайны. Но карантин создаёт и риск: если вы не следите за карантинным набором, он разрастается и превращается в кладбище тестов, которые никто не чинит.

Автоматические перезапуски

Многие CI-системы поддерживают перезапуск упавших тестов: тест бежит до трёх раз, и если хотя бы раз проходит — считается зелёным. Это снижает шум, но маскирует проблему. Если тест проходит со второй попытки — он flaky, и его нужно чинить, а не перезапускать. Перезапуски — временное обезболивающее, а не лечение.

Детекция изменений flakiness

Google разработал инструмент, который отслеживает изменения в уровне flakiness каждого теста и пытается привязать их к конкретным коммитам. Если тест стал flaky после определённого изменения — автор изменения получает уведомление. Это позволяет ловить проблему «по горячим следам», пока автор ещё помнит контекст.

Изоляция тестов

Каждый тест бежит в чистом окружении: свежий контейнер, чистая база, изолированная сеть. Это устраняет зависимость от состояния и порядка выполнения, но увеличивает время CI (поднятие контейнера на каждый тест — дорого) и требует инфраструктурных вложений.

Метрики для flaky-тестов

Чтобы понять масштаб проблемы в вашей команде, начните с трёх метрик.

Flakiness rate — процент тестов, которые показали flaky-поведение за последние 30 дней. Если он больше 5%, вы теряете доверие к CI. Если больше 15% — CI как инструмент обратной связи по сути не работает.

Re-run rate — как часто разработчики перезапускают пайплайн. Каждый перезапуск — это и потерянное время, и индикатор того, что красный CI воспринимается как шум.

Time to fix — сколько времени flaky-тест проводит в карантине или в состоянии «известная проблема» до починки. Если средний time to fix — больше двух недель, ваш карантин превращается в вечное хранилище.

Рекомендации

Начните с измерения: добавьте в CI логирование информации о перезапусках и flaky-результатах. Большинство CI-систем (GitHub Actions, GitLab CI, Jenkins) имеют плагины или встроенные возможности для трекинга flaky-тестов. Atlassian в 2025 году описал, как они построили масштабируемый инструмент для детекции и управления flaky-тестами, который интегрируется с CI и автоматически группирует flaky-тесты по root cause.

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

И помните: цель — не ноль flaky-тестов (это нереалистично в системе с тысячами тестов), а быстрая детекция и починка. Google при своих масштабах не смог полностью победить flakiness, но построил систему, которая минимизирует его влияние на продуктивность инженеров.