flaky test 관리법, 재시도를 측정 도구로 바꾸는 순서

플래키 테스트 관리법, 재시도를 측정 도구로 바꾸는 순서

3줄 요약

  • 플래키는 재시도 설정으로 정리되는 작은 문제로 여기기 쉽습니다.
  • Bitrise 집계로 플래키를 겪는 팀은 10%에서 26%로 늘었습니다.
  • 재시도는 측정에만 쓰고 집계와 격리 레인부터 두십시오.

9월 초에 pytest-rerunfailures 재시도가 JUnit XML에 흔적을 남기지 않는 문제를 정리한 적이 있는데요. 그때도 저는 재시도 자체는 CI를 살려 주는 안전장치 정도로 봤습니다. 흔적만 따로 남기면 켜 둬도 괜찮다고요.

이번 주 일간 리포트를 월요일에 모아 읽고 나서 그 생각을 접었습니다. 플래키를 연달아 다룬 사흘 치(9월 25~27일)가 날마다 원인을 한 칸씩 아래로 내리고 있었거든요. 재시도와 격리 레인에서 데이터 격리로, 다시 테스트 잠금으로요.

먼저 밝혀 둡니다. 이 글은 자동 수집된 일간 QA 리포트 7건(9월 22일~28일)을 취합한 것이고, 원문의 수치를 1차 소스로 다시 확인하지 않았습니다. 각 리포트가 스스로 "독립 검증된 근거가 없습니다"로 표시한 수치는 본문에서 뺐습니다.

1. 플래키가 줄지 않는다는 숫자부터

한 주 리포트에서 플래키 쪽 수치는 25일자에 몰려 있었습니다. 그중 하나는 27일자가 다시 꺼냈습니다.

Bitrise 빌드 1,000만 건에서 본 증가

25일자 리포트는 Bitrise Mobile Insights 분석(빌드 1,000만 건 이상, 3.5년)을 인용해, 플래키를 겪는 팀의 비율이 2022년 10%에서 2025년 26%로 늘었다고 적었습니다. 같은 기간 파이프라인 복잡도는 23% 늘었다고 함께 적었습니다. 27일자는 이 수치를 모바일 자동화 맥락에서 한 번 더 인용했습니다.

두 수치를 나란히 두었을 뿐, 리포트는 복잡도 증가가 플래키 증가의 원인이라고 말하지 않았습니다. 여기서도 인과로 읽지 않겠습니다.

원인의 절반 가까이는 기다리는 방식입니다

25일자가 원인 분포와 대응 사례를 함께 실었습니다. 인용 주체를 칸으로 따로 뺐습니다.

수치 무엇을 센 값인가 인용 주체 리포트 날짜
10% → 26% 플래키를 겪는 팀 비율 (2022 → 2025) Bitrise Mobile Insights 09-25, 09-27
45% · 20% · 12% · 8% 원인: 비동기 대기 · 동시성 · 테스트 순서 의존 · 리소스 누수 Luo et al. 09-25
84% pass → fail 전환 중 실제 회귀가 아닌 플래키 Google 사례 09-25
18% 감소 "2주 내 수정 또는 제거" 정책을 6개월 운영한 결과 Microsoft 09-25

비동기 대기 45%는 가장 큰 덩어리가 기다리는 방식에서 나온다는 뜻입니다. 25일자가 액션 3번으로 "고정 sleep/wait 전수 검색 → 조건 대기로 교체"를 올리면서 기대 효과를 "플래키 최대 원인(45%) 직접 제거"로 적은 것도 이 숫자에서 나왔습니다.

Google 사례의 84%는 결이 다릅니다. 빨간불 대부분이 회귀가 아니었다는 뜻이고, 그런 파이프라인에서는 팀이 빨간불을 흘려보내는 습관이 들기 쉽다고 봅니다.

개수가 곱해지면 초록불이 드물어집니다

25일자는 TestDino 플래키 벤치마크가 인용한 David Gomes 사례로 아래 표를 실었습니다.

테스트당 플래키율 테스트 수 파이프라인 적색 확률
0.01% 100 약 1%
0.01% 1,000 약 10%
0.01% 4,000 약 33%
0.03% 4,000 약 70%

숫자가 과장처럼 보여서 직접 계산해 봤습니다. 테스트끼리 독립이라고 두면 적색 확률은 1 - (1 - p) ** n입니다.

# TestDino 표(테스트당 플래키율 × 테스트 수 → 적색 확률)를 재계산합니다
# 테스트끼리 독립이라는 가정은 이 글에서 붙인 것입니다 (리포트 표에는 가정이 적혀 있지 않습니다)
for p, n in [(0.0001, 100), (0.0001, 1000), (0.0001, 4000), (0.0003, 4000)]:
    print(f"플래키율 {p*100:.2f}% × {n:>5}개 → 적색 확률 {1 - (1 - p) ** n:.1%}")

결과는 1.0% · 9.5% · 33.0% · 69.9%로 표의 네 줄과 맞았습니다. 테스트 하나의 플래키율이 아무리 낮아도 개수가 늘면 스위트 전체의 초록불은 빠르게 드물어진다는 뜻입니다.

자동화 초기 팀이라 아직 스위트가 수백 개 수준이어도 이 식은 개수가 늘 때마다 한쪽으로만 움직입니다. 25일자가 "스위트가 커지기 전에 격리 규칙을 먼저 만드는 것이 저렴하다"고 적은 것과 같은 방향입니다.

2. 사흘 동안 원인 설명이 한 칸씩 내려갔습니다

25일부터 27일까지 세 건이 연달아 플래키를 다뤘는데, 날마다 가리킨 원인이 달랐습니다. 한 줄씩 세우면 이렇습니다.

날짜 리포트가 짚은 원인 내놓은 수단 리포트가 붙인 단서
09-25 비동기 대기 — 초록불 자체를 믿을 수 없음 격리 레인 + 매니페스트, 재시도는 측정용 (--fail-on-flaky-tests) 격리 상한(예: 전체의 3%)은 리포트의 제안
09-26 공유 데이터 오염 Playwright 1.62 retryStrategy: "isolated", 이름 붙인 고정 픽스처 + 생성·정리 규칙 옵션명과 동작은 공식 릴리스 노트에서 재확인 권고
09-27 공유 자원 충돌 (공유 계정·전역 설정) Playwright 1.63 named test lock, trace의 aria·screen 스냅샷 fullyParallel 설정이 전제, Ubuntu 20.04 지원 종료

원인이 기다림에서 데이터로, 데이터에서 공유 자원으로 내려가는 동안 수단도 CI 설정에서 테스트 설계 쪽으로 옮겨 갔습니다. 27일자 스스로 "1.62의 isolated retry와 1.63의 test lock은 같은 플래키 대응 흐름"이라고 묶었습니다.

하루치만 읽으면 셋 중 하나가 정답처럼 보입니다. 사흘을 붙여 놓아야 이것이 한 문제를 위에서 아래로 파 내려간 기록이라는 게 보입니다.

25일 — 재시도는 고치는 수단이 아니라 재는 수단

25일자의 요지는 재시도를 켜는 순간 초록불의 뜻이 바뀐다는 것입니다. 안정 게이트에서 재시도 후 통과도 실패로 처리해야 초록불의 의미가 유지된다고 적었습니다.

리포트가 실은 설정 예시입니다. 리포트 표기 그대로이고 더하거나 뺀 것이 없습니다.

// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
// CI에서만 1회 재시도: 수정이 아니라 '간헐성 측정' 목적
retries: process.env.CI ? 1 : 0,
use: {
// 첫 재시도 시 트레이스 수집 → 원인 분석 근거 확보
trace: 'on-first-retry',
},
});
// 실행 예시 (안정 게이트: 격리 태그 제외 + 재시도 후 통과도 실패 처리)
// npx playwright test --grep-invert @quarantine --fail-on-flaky-tests
// 격리 레인(병합 비차단, 결과는 대시보드로 수집)
// npx playwright test --grep @quarantine

핵심은 레인이 두 개라는 점입니다. @quarantine 태그를 뺀 안정 게이트는 병합을 막고, 격리 레인은 따로 돌려 결과만 모읍니다.

재시도는 켜 두되 그 결과를 통과로 세지 않는 구조입니다. pytest-rerunfailures 글에서 결국 따로 뽑아야 했던 값도 이 "재시도 후 통과" 건수였습니다.

26일 — 같은 데이터를 두 테스트가 만질 때

26일자는 원인을 한 칸 아래로 내렸습니다. 리포트는 전날 다룬 플래키의 상당 부분이 공유 데이터 오염에서 온다고 봤습니다.

수단은 두 가지였습니다. 하나는 Playwright 1.62에 들어갔다는 retryStrategy: "isolated"로, 리포트는 재시도를 격리 실행해 상태 오염으로 생기는 가짜 통과를 막는 옵션으로 소개했습니다. 같은 리포트가 옵션명과 동작을 공식 릴리스 노트에서 재확인하라고 적어 두어서, 이 글에서는 설정 예시로 옮기지 않았습니다.

다른 하나는 도구가 아니라 규칙입니다. premium_user_1, expired_card_5처럼 이름 붙인 고정 픽스처 목록과 테스트별 생성·정리 규칙을 문서 한 장으로 정하라는 권고였고, 판단 표에서 도입·유지보수·학습곡선이 모두 가장 낮은 칸이었습니다.

27일 — 충돌하는 테스트만 줄 세우기

27일자는 Playwright 1.63(9월 4일)의 named test lock을 가져왔습니다. 같은 lock 이름을 단 테스트는 파일·워커·프로젝트를 넘어 동시에 돌지 않는다는 기능입니다.

리포트가 짚은 장면은 스타트업에 흔한 것이었습니다. 스테이징 공유 계정이나 전역 설정 때문에 workers: 1이나 serial 모드로 전체를 느리게 돌리는 경우입니다. lock은 충돌하는 테스트만 직렬화하므로 속도 손실 없이 그 플래키를 없앨 수 있다는 설명이었습니다.

단서도 둘 붙었습니다. 기본·serial 모드에서는 lock이 파일 전체에 걸리므로 fullyParallel이 전제라는 것, 그리고 1.63이 Ubuntu 20.04 지원을 끝냈으니 러너 OS부터 확인하라는 것입니다.

lock 문법은 리포트 예시로만 봤고 공식 문서와 대조하지 않았습니다. 그래서 코드는 옮기지 않고, 러너 OS 확인만 4절의 점검 명령에 넣었습니다.

3. 그래서 4~6명 팀은 재시도보다 격리 레인을 먼저 둡니다

세 날의 수단을 비용과 의존 관계로 다시 줄 세우면 순서가 나옵니다. 아래는 25~27일자 평가표에서 플래키 관련 행만 모은 판단 표입니다.

수단 도입 난이도 유지보수 비용 팀 학습곡선 리포트 권고 날짜
첫 시도 통과율 / 재시도 후 통과 분리 집계 낮음 낮음 낮음 즉시 — 기존 리포트 파싱만으로 가능 09-25
격리 레인 (pytest 마커·Playwright 태그 + YAML 매니페스트) 낮음 낮음 낮음 즉시 — 도구 구매 불필요 09-25
경량 TDM (Faker + 고정 픽스처 + factory/teardown) 하 하 하 권장 — 플래키 감소와 직결 09-26
Playwright 1.62 업그레이드 (isolated retry) 하 하 하 권장 09-26
Playwright 1.63 test lock + aria trace 낮음 낮음 낮음 즉시 — CI OS가 Ubuntu 22.04 이상인지 먼저 확인 09-27
테스트 관측성 SaaS (Trunk, BuildPulse, Datadog CI Visibility 등) 낮음 중간 (구독료) 낮음 보류 후 재평가 — 스위트 수백 개 이상일 때 09-25

표만 보면 거의 모든 칸이 낮음이라 순서가 없어 보입니다. 순서를 가르는 건 비용이 아니라 의존 관계입니다.

첫 시도 통과율을 따로 세지 않으면 격리 레인에 무엇을 넣을지 고를 근거가 없습니다. 격리 레인이 없으면 lock이나 isolated retry를 켠 효과를 비교할 기준선이 없습니다. 그래서 집계 → 격리 → 데이터 규칙 → 버전 업그레이드 순서로 판단했습니다.

관측성 SaaS는 25일자도 보류로 적었습니다. 우리 규모에서는 스위트가 수백 개를 넘고 주간 재실행 부담이 커질 때 다시 보면 됩니다.

격리 레인은 skip과 다릅니다

25일자가 가장 힘주어 적은 문장은 "격리는 skip이 아니다"였습니다. 출처 목록에 OneUptime의 격리 가이드 등을 올리고, 격리한 테스트도 계속 돌리되 병합을 막지 않는 별도 레인에서 돌리라고 했습니다.

매니페스트에는 테스트 ID·이슈 URL·소유자·만료일·실패 시그니처를 적고, 만료된 항목은 CI가 거부합니다. 리포트는 여기에 격리 개수 상한(예: 전체의 3%)을 OKR 가드레일로 걸자고 제안했습니다.

만료일이 없는 격리는 결국 skip과 같아집니다. Microsoft의 "2주 내 수정 또는 제거" 정책도 같은 칸, 곧 기한을 건드린 사례로 읽었습니다.

첫 시도 통과율을 따로 세는 일

25일자 액션 1번은 CI 리포트에서 "첫 시도 통과율"과 "재시도 후 통과 건수"를 분리해 대시보드에 넣는 것이었고, 소요는 1~2일로 적혀 있었습니다. 26일자 설정 예시도 JSON 리포터를 켜 두면서 주석에 "JSON으로 첫 시도 통과율 집계"라고 적었습니다.

25일자는 AI 테스트를 들일 때의 기본 KPI로도 "AI 생성 테스트의 첫 시도 통과율"을 꼽았습니다. 이 지표 하나가 플래키 대응과 AI 테스트 평가 양쪽의 기준선이 되는 셈입니다.

4. 리포트의 '최신 버전'이 늦게 따라온 지점

한 주 치를 모아 읽어야만 보이는 어긋남이 하나 더 있었습니다. 같은 도구의 "최신" 표기가 날짜마다 달랐습니다.

도구 앞선 날짜의 표기 뒤 날짜의 표기
Playwright 09-22 "최신 안정 1.62.1(2026-07-30)" · 09-24 "현행 v1.62(2026년 7월)" · 09-26 1.62를 이번 주 변경으로 소개 09-27 "1.63(2026.9.4)" · 09-28 "최신 1.63, 1.64 발표는 확인된 근거 없음"
Selenium 09-25 "최신 안정 4.46.0(2026.7.11)" · 09-26 "4.47.0(2026.8.10) 안정판"(일자 1차 출처 미확인) 09-27 "4.49(2026.9.9)" · 09-28 "4.49 최신"
Appium 09-24 "3.6.0(2026.7.25)" · 09-26 "3.7.0(2026.8.24), 3.8.0은 9월 25일경 게시·세부 내역 확인 근거 없음" 09-27 "3.8.0(2026.9.24)"

27일자가 적은 날짜가 맞다면, 22일·24일자가 최신이라 부른 1.62는 그 시점에 이미 한 버전 뒤였습니다. 어느 쪽도 1차 소스로 확인하지 않았으므로 판정은 하지 않겠습니다. 26일자는 Selenium 4.47.0 날짜와 Appium 3.8.0 세부 내역의 1차 출처를 확인하지 못했다고 스스로 밝혀 두었습니다.

업그레이드 전에 직접 확인하는 명령

동향 리포트의 버전 표기는 참고로만 두고, 저장소와 레지스트리에서 직접 확인하는 편이 빠릅니다. 1.63의 Ubuntu 20.04 지원 종료도 여기서 같이 봅니다.

# 저장소에 설치된 Playwright 버전 (Playwright CLI)
npx playwright --version
# lock 파일 기준으로 실제 해석된 버전
npm ls @playwright/test
# npm 레지스트리의 최신 배포 버전 — 리포트의 '최신' 표기를 믿기 전에 확인
npm view @playwright/test version
npm view appium version
# CI 러너 OS — 09-27 리포트: 1.63부터 Ubuntu 20.04 지원 종료
lsb_release -ds

26일자는 초기 팀에 가장 흔한 사고로 "CI 이미지의 버전이 자동으로 올라가 어느 날 전부 빨간불"이 되는 경우를 꼽고, 버전 lock과 월 1회 업그레이드 데이를 권했습니다. 초록불을 흔드는 원인이라는 점에서 플래키와 같은 묶음에 넣었습니다.

5. 같은 주에 함께 나온 흐름

플래키 말고도 3일 이상 되돌아온 주제가 세 개 있었습니다. 이번 글의 중심은 아니어서 표 하나로 압축합니다.

주제 나온 날 리포트가 든 근거 한 주 안의 변화
AI 코드 증가와 QA 병목 09-22·24~28 (6일) Tricentis 조사(2,500명 이상)에서 60%가 미검증 코드 배포 · DeviQA 조사(QA 4,000명, 자기보고)에서 55%가 QA 대기열 증가 전수 테스트 대신 리스크 기반 선별(25·28일)과 핸드오프 DoD(26일)로 수렴
계약 테스트 09-22~28 (7일) 7일 모두 본문에 등장, 판단 표 행은 22·23·24·27·28일 23일 "Pact 최우선" → 24일 Schemathesis 즉시 파일럿·Pact 조건부 → 27·28일 스펙 보유 시 OpenAPI 스키마 기반 1순위, Pact는 핵심 관계에만
AI가 만든 테스트의 판정 09-22·23·25·26·27·28 (6일) 27일 뮤테이션·독립 오라클, 28일 "AI 리뷰어는 독립적 보증이 아닙니다" "생성은 AI, 판정 검증은 기계+사람"(27일)으로 정리

DeviQA 조사는 자사 커뮤니티의 자발적 응답이고 AI가 유일한 원인이라고 주장하지 않는다는 한계를, 26일자 리포트가 보고서 자체의 설명을 빌려 밝혀 두었습니다.

계약 테스트 권고가 또 움직였습니다

지난주 정리에서는 Pact 권고가 나흘 사이 보류에서 1순위로 뒤집힌 것을 다뤘습니다. 이번 주에는 방향이 반대였습니다.

23일자는 Pact를 최우선 권장으로 적고 1순위 액션에 can-i-deploy 게이트를 올렸습니다. 바로 다음 날 24일자는 OpenAPI 스펙이 있다면 Schemathesis 반나절 파일럿을 1순위 액션에 넣고 Pact는 서비스 2개 이상일 때로 조건을 달았습니다. 27·28일자는 OpenAPI 스펙이 있으면 스키마 기반 계약 테스트를 1순위로 올리고, Pact는 도입 난이도 높음으로 적은 뒤 핵심 관계 2~3개(27일)·1~2개(28일)에만 쓰라고 내렸습니다.

27일부터는 두 방식을 2단 조합(스키마 우선이 넓은 커버리지, Pact가 핵심 관계의 정밀 보호)으로 보는 서술이 나왔습니다. 지난주의 "보류냐 1순위냐"라는 질문이 이번 주에는 "어느 층부터냐"로 바뀐 셈입니다.

AI가 만든 테스트는 누가 채점하나

27일자는 코드와 테스트를 같은 AI가 만들면 스스로 채점하는 구조가 된다고 짚고, 뮤테이션 테스트를 현실적인 판정 도구로 들었습니다. 28일자는 두 번째 AI 리뷰어가 속도는 올려도 독립성은 만들지 않는다는 권고를 옮겼습니다.

플래키와 맞닿는 지점은 하나입니다. 25일자는 AI가 생성한 E2E에 sleep 기반 대기가 섞이는 경우가 많다며 플래키 관리 체계와 반드시 함께 운영하라고 했습니다. AI 테스트를 들이기 전에 격리 레인이 먼저 있어야 하는 이유입니다.

6. 이번 주 팀 회의에서 정할 것

일곱 건을 다 읽고 나니 새로 들일 도구는 거의 없더라고요. 정할 건 규칙 몇 줄입니다.

저라면 재시도 설정은 그대로 두고요, 첫 시도 통과율 집계부터 붙이겠습니다. 격리 레인은 그다음이고, lock과 isolated retry는 러너 OS와 버전을 확인한 뒤에 켜도 늦지 않습니다.

□ CI 결과에서 첫 시도 통과율과 재시도 후 통과 건수를 따로 집계한다 (09-25 액션 1)
□ 안정 게이트는 재시도 후 통과도 실패로 센다
□ 격리 매니페스트에 테스트 ID · 이슈 · 소유자 · 만료일을 적고, 만료 항목은 CI에서 거부한다
□ 격리 개수 상한을 정한다 (리포트 제안: 전체의 3%)
□ E2E 코드에서 고정 sleep을 검색해 조건 대기로 바꾼다 (AI가 만든 테스트부터)
□ 이름 붙인 고정 테스트 계정 목록과 생성·정리 규칙을 문서 한 장으로 정한다
□ 공유 계정을 건드리는 E2E 목록을 뽑아 둔다 (lock 도입 후보)
□ 업그레이드 전에 러너 OS와 lock 파일의 실제 버전을 확인한다

이 중 4~6명 팀이 이번 주에 끝낼 수 있는 건 위 세 줄 정도인데요, 25일자가 적은 소요로는 집계 1~2일, 매니페스트 2~3일입니다. 나머지는 다음 주 리포트가 또 방향을 바꾸는지 보면서 가도 됩니다.

한계도 적어 둡니다. 이 글의 수치는 전부 일간 리포트가 인용한 값이고, 원 출처를 다시 열어 보지 않았습니다. 리포트가 스스로 미검증이라 표시한 수치(자가 치유 도구의 실패 제거율, 모바일 프레임워크별 플래키율, Playwright 이전 후 플래키 감소율 등)는 모두 뺐습니다.

코드가 바뀌지 않았는데 테스트가 깨지는 다른 유형은 회귀 테스트가 조용히 깨질 때 원인을 찾는 글에 있습니다.

댓글

이 블로그의 인기 게시물

옵시디언 3기기 동기화, 유료 결제 없이 구글 드라이브로 끝내기

최저가 주유소, 얼마나 돌아가면 손해일까?

회의시간 줄이는 법, 개수보다 배치 먼저

유류세 인하 2개월 연장, 주유소 가격 실제 인하폭 계산

국민연금 추납 조회, 미루면 얼마나 더 낼까?