Карантин и flaky-тесты: пожарная тревога, которая врёт
Сцена · пожарная тревога, которая врёт
В наборе кассы есть тест, который падает примерно раз в пять прогонов. Код не менялся, тест не менялся, но иногда красный. Первый раз ты вглядываешься, не находишь причины, перезапускаешь, и он зелёный. Второй раз просто перезапускаешь. К десятому разу ты, не глядя, жмёшь “retry” на любом красном CI.
Вот тут и кроется главная опасность. Flaky-тест опаснее, чем вообще отсутствие теста. Отсутствующий тест честно молчит. Flaky врёт: он орёт красным, когда всё в порядке, и команда привыкает игнорировать красное. А в тот день, когда покраснеет настоящий баг, его пролистают вместе с flaky. Пожарная сигнализация, которая срабатывает зря, кончается тем, что её отключают, и тогда пропускают настоящий пожар.
Карта урока · что заберёшь
- поймёшь, почему flaky опаснее отсутствия теста и как он разрушает доверие;
- разберёшь четыре источника flakiness: время, порядок, гонка, сеть;
- освоишь карантин как срок на починку, а не как кладбище;
- научишься искать корневую причину прогоном в цикле, а не глушить retry.
Раздел 1 · источники flakiness и карантин
Идея словами
Flaky не берётся из ниоткуда. Почти всегда это один из четырёх источников недетерминизма: время (waitForTimeout вместо ожидания состояния), порядок (тест опирается на данные соседа), гонка (два воркера пишут в одну строку), сеть (реальный вызов внешнего API). Пока источник не устранён, тест будет врать, сколько ни перезапускай.
Картинка · источник, карантин, починка
Виджет показывает один E2E “оплата проходит и баланс верный”. Выбери источник нестабильности и прогони десять раз: увидишь смесь зелёного и красного на одном и том же коде. Отправь в карантин: он перестанет блокировать мердж, но останется на глазах. Потом “почини причину” и прогони снова: десять из десяти зелёных, потому что устранён источник, а не добавлен retry.
e2e: оплата проходит и баланс верныйблокирует мерджПричина: waitForTimeout(500): иногда сервер не успел ответить за 500 мс.
Карантин убирает flaky из блокеров мерджа, но держит его на глазах с тикетом, чтобы чинили, а не удаляли. Зелёным набор делает не retry, а устранение источника: попробуй сначала прогнать flaky, потом «починить причину» и прогнать снова.
Шаг 1 · карантин это срок, а не кладбище
Когда тест flaky, у тебя три плохих и один хороший вариант. Плохие: удалить (потеряешь покрытие), игнорировать красное (отравишь весь набор), завернуть в retry (спрячешь баг под ковёр). Хороший: карантин.
Помеченный flaky уходит в отдельную группу, которая гоняется, видна, но не красит PR. На него заводят тикет с дедлайном. Карантин это срок на починку, а не кладбище: тест не удаляют (покрытие важно) и не глушат retry (баг важен), а держат на виду, пока не найдут причину.
Шаг 2 · карантин в CI
В пайплайне Kassa карантин это отдельная job с continue-on-error, которая гоняет помеченные тесты, но не блокирует мердж:
# .github/workflows/ci.yml
quarantine:
needs: test
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
- run: pnpm install --frozen-lockfile
- run: pnpm e2e:install
- run: pnpm e2e --grep "@quarantine"
continue-on-error: true делает группу видимой (она появляется в списке проверок), но её краснота не валит PR. Помечаешь flaky-тест тегом @quarantine, он уезжает сюда. Блокирующие job (test, mutation, e2e без тега) остаются строгими.
Что взять с собой
- flaky опаснее отсутствия теста: он учит команду игнорировать красное;
- источников четыре: время, порядок, гонка, сеть;
- карантин выводит flaky из блокеров, но держит на глазах с тикетом, не удаляя и не глуша retry.
дальше после входа
Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.
Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.