Flaky-тесты: проблема и подходы — Лаборатория DX
Mid Авторский

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

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

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

Flaky Tests at Google and How We Mitigate Them

John Micco, 2016

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

Кнопка «re-run» на упавшем пайплайне, тесты прошли, 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 тестов flakiness rate в 1,5% означает, что при каждом прогоне порядка 15 тестов падают по причинам, не связанным с изменениями. Если CI блокирует мерж на любом красном тесте, разработчики будут гонять пайплайн по кругу и тратить часы в день на ожидание.

Почему тесты становятся 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 и автоматической группировкой по root cause.

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

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