Flaky-тесты: проблема и подходы
Почему нестабильные тесты разрушают доверие к CI и как с ними бороться
Когда 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, но построил систему, которая минимизирует его влияние на продуктивность инженеров.