Слогер Создать блог
Разработка

Как выбрать, какой flaky-тест чинить первым: метрика и практика

Когда в проекте три десятка ненадёжных тестов, главный вопрос — не «флакает ли тест», а «какой из них чинить в ближайший четверг». Обычный процент падений тут не поможет: он не отличает сломанный тест от периодически сбоящего и ничего не говорит о цене каждого флака. Разбираем метрику, которая всё это учитывает.

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

Почему «падения на сотню запусков» — плохой ответ

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

Второй недостаток тоньше. Метрика считает только долю падений и выбрасывает порядок наблюдений. Тест, который упал десять раз во вторник и с тех пор зелёный, приравнивается к тесту, который падает дважды в неделю стабильно. Это разные ситуации с разными действиями: первый — уже разрешённый инцидент, второй — постоянный налог. Хорошая метрика должна их различать.

Обе проблемы лечатся одним способом: перестать игнорировать порядок исходов.

Flip rate: что это и зачем

Упорядочим запуски одного теста по времени и посмотрим на последовательность результатов. Возьмём два теста с одинаковой долей падений:

ПоказательТест AТест B
Доля падений0.500.50
Число переходов (flips)113
Flip rate1/19 = 0.0513/19 = 0.68

Тест A — это не флаки. Он сломался в конкретный момент и с тех пор падает всегда: классическая регрессия, находится бисекцией, у неё есть первый плохой коммит. Тест B — по-настоящему нестабилен. Доля падений тут бессильна, flip rate разделяет их полностью.

Считается просто:

flip_rate = количество переходов / (запуски - 1)

Переход — это позиция, где результат в запуске i отличается от результата в запуске i-1. Запуски упорядочены по времени коммита.

У flip rate есть естественный потолок. Идеально чередующаяся последовательность даёт 1.0. Тест, который ведёт себя как подбрасывание монетки с 50% падений, в среднем покажет около 0.5. Если значение приближается к единице, это не случайный шум, а что-то, что систематически переключается: зависимость от порядка тестов, общий ресурс, который кто-то переключает, два узла деплоя с разными версиями модели. Это другой баг и другое исправление, и метрика сразу указывает на него.

Проблема малых выборок

Тест с четырьмя запусками и одним флаком имеет точечную оценку 25%. Тест с четырьмястами запусками и сорока флаками — 10%. Если ранжировать по сырой доле, первый окажется выше второго. Это неправильно: про первый тест вы почти ничего не знаете, про второй — очень много. Нужно ранжировать по нижней границе доверительного интервала, а не по оценке.

Обычный выбор — нижняя граница интервала Уилсона. Она ведёт себя разумно, когда число падений равно нулю или доля близка к границе, где нормальное приближение врёт. Код:

from math import sqrt def wilson_lower_bound(successes, n, z=1.96): """Нижняя граница интервала Уилсона.""" if n == 0: return 0.0 phat = successes / n denom = 1 + z * z / n centre = phat + z * z / (2 * n) margin = z * sqrt((phat * (1 - phat) + z * z / (4 * n)) / n) return (centre - margin) / denom wilson_lower_bound(1, 4) # тест из 4 запусков, 1 флак wilson_lower_bound(40, 400) # тест из 400 запусков, 40 флаков

Эффект: чтобы тест поднялся в списке, нужны доказательства. Один флак из четырёх запусков опускается вниз, пока не накопит достаточно истории. Умеренная, но стабильная доля на сотнях запусков поднимается выше. Это та же поправка, что используется при ранжировании долей где угодно, и именно она превращает список новичков в список реальных проблем.

Применяйте нижнюю границу к flip rate, а не к доле падений — ведь ранжировать хотите именно по ней. Учтите: знаменатель на единицу меньше числа запусков, это заметно только для очень малых выборок, а их эта граница как раз и призвана понижать, так что приближение безвредно. Выберите один z и не меняйте его: смена z между отчётами перетасует рейтинг по причинам, не связанным с тестами.

Ранжирование по ущербу, а не по доле

Доля сама по себе — ещё не приоритет. Два теста с одинаковой долей могут стоить совершенно по-разному. Умножьте на то, сколько хлопот причиняет каждый флак:

score = wilson_lower_bound(flaky_runs, total_runs) * runs_per_week * blast_radius blast_radius: 4 — блокирует мердж в основной ветке 2 — блокирует мердж в фиче-ветке 1 — ночной прогон, никого не блокирует

Теперь у единиц есть смысл: «ожидаемые заблокированные мерджи в неделю». Тест с 30% флаков, который запускается раз ночью, опустится ниже теста с 3% флаков, который запускается на каждый пуш в оживлённый репозиторий. Это правильный порядок, и сортировка по доле его не даст.

Уровни blast radius намеренно грубые. Трёх достаточно, чтобы расставить порядок, и их мало, чтобы никто не спорил полдня, заслуживает ли тест двойку с половиной. Не добавляйте слагаемое за важность фичи: каждый считает свою фичу самой важной, и итоговый балл перестанет быть сравнимым между командами. Эта метрика измеряет стоимость флака, а не ценность фичи.

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

Как эту метрику используют неправильно

  • Как целевой показатель. Если инженеров оценивают по этому баллу, они будут снижать его удалением тестов. Используйте его для ранжирования, но не отчитывайтесь по нему как по метрике команды.
  • Сравнение несовместимых окон. Нельзя сравнивать тест, живущий год, с тестом, добавленным во вторник. Для обоих нужно одинаковое окно, иначе доверительная граница считает разные вещи.
  • Без исключения известных инцидентов. Четырёхчасовой сбой провайдера перевернёт все тесты в наборе и перемешает весь рейтинг. Помечайте такие запуски по сигнатуре и исключайте: сбой — не свойство теста.
  • В наборе без ретраев. Если на каждый запуск только одна попытка, флак и полноценное падение — одно и то же событие. Вся метрика схлопывается в долю падений со всеми исходными проблемами.

Вывод

Чтобы выбрать, какой flaky-тест чинить первым, нужен балл, а не доля. Считайте переходы между успехом и падением, применяйте нижнюю границу доверительного интервала для малых выборок и умножайте на частоту запусков и цену блокировки мерджа. И не превращайте балл в цель — иначе останетесь без тестов, но с чистой метрикой.

По материалам: ai. Текст переработан редакцией Слогера.

← На главную

Рекламное место — Конец поста
Реклама · Слогер

Комментарии (0)

Войдите, чтобы комментировать.

Пока нет комментариев. Будьте первым.