「테스트가 실패해도 진짜 버그인지 알 수 없다」「CI가 빨간색이어도 아무도 신경 쓰지 않게 됐다」——그 Flaky 테스트 지옥에서 탈출하는 방법을 원인 분류·진단·대책·재발 방지까지 실무 경험을 바탕으로 해설합니다. Wait 설계·환경 차이·데이터 경합·외부 의존 등, Flaky의 종류별로 우선순위를 두고 대책을 소개합니다.
Flaky 테스트의 진짜 두려움은 「테스트가 실패한다」는 것이 아니라, 「테스트 결과를 아무도 신뢰하지 않게 된다」는 것입니다.
📌 이런 분께 추천합니다
- 「CI가 실패해도 일단 재실행」이 습관이 된 QA 엔지니어
- Flaky가 너무 많아 테스트 결과에 대한 신뢰가 사라진 팀 리드
- Flaky가 「왜 일어나는지」 원인을 체계적으로 이해하고 싶은 분
- Flaky의 재발 방지까지 포함한 근본 해결을 목표로 하는 분
✅ 이 기사를 읽으면 얻을 수 있는 것
- Flaky 테스트의 종류 분류와 각각의 근본 원인을 알 수 있다
- 「어떤 Flaky부터 우선적으로 고칠 것인가」의 판단 기준을 알 수 있다
- 원인별 구체적인 대책 코드와 재발 방지 설계 패턴을 알 수 있다
👤 이 기사를 쓴 사람
QA 엔지니어·테스트 자동화 엔지니어로서 15년 이상의 실무 경험을 가진 Yoshi가 집필. 「CI가 빨간색이어도 아무도 신경 쓰지 않는」상태까지 악화된 Flaky 테스트 지옥을 여러 프로젝트에서 경험·복구한 실체험을 바탕으로 해설합니다.
📖 관련 기사와의 사용 구분
- 테스트 자동화에서 자주 발생하는 실패 5선:Flaky를 포함한 5가지 실패를 개설 → 자동화 전체의 실패 패턴을 알고 싶은 분은 이쪽
- Selenium 운용 붕괴 이야기 7선:Selenium의 Wait 혼재·ChromeDriver 원인의 Flaky → Selenium 특유의 Flaky는 이쪽
- 이 기사:도구 비의존으로 Flaky의 원인을 분류하고, 진단부터 근본 해결·재발 방지까지 체계적으로 해설
📌 결론 (3가지 포인트)
- Flaky 테스트는 Wait·환경·데이터·외부 의존·설계의 5종류로 분류할 수 있으며, 종류가 다르면 대책도 다르다
- 「일단 재실행」은 응급처치. 근본 해결은 「어떤 종류의 Flaky인가」를 특정하는 것부터 시작한다
- Flaky가 사라지지 않는다면 「자동화해서는 안 되는 테스트」일 가능성이 있다. 삭제도 정답 중 하나
「또 Flaky야」——그 한마디가 일상이 됐을 때, 테스트 자동화는 기능을 잃었습니다. CI가 빨간색이어도 「어차피 Flaky겠지」라고 재실행할 뿐. 진짜 버그를 놓칠 위험이 높아지고, 결국 아무도 테스트 결과를 신뢰하지 않게 됩니다.
이 기사에서는 Flaky 테스트를 「대충 고치는」것이 아니라, 원인을 종류별로 분류하여 체계적으로 탈출하는 방법을 해설합니다.
- Flaky 테스트란 무엇인가?왜 위험한가
- Flaky 테스트의 종류와 근본 원인이란?5분류
- 먼저 확인:증상에서 Flaky 종류를 진단한다면?
- STEP 1:먼저 Flaky를 계측·가시화한다란?
- STEP 2:① Wait 계 Flaky의 대책이란?
- STEP 3:② 환경 의존 계 Flaky의 대책이란?
- STEP 4:③ 데이터 경합 계 Flaky의 대책이란?
- STEP 5:④ 외부 의존 계 Flaky의 대책이란?
- STEP 6:⑤ 설계 기인 계 Flaky의 대책이란?
- STEP 7:고칠 수 없는 Flaky는 격리·삭제를 검토한다란?
- Flaky 테스트의 재발 방지:설계 단계에서 할 것
- FAQ
Flaky 테스트란 무엇인가?왜 위험한가
Flaky 테스트(Flaky Test)란, 「같은 코드로 같은 환경일 텐데, 실행할 때마다 합격/불합격이 바뀌는 불안정한 테스트」를 말합니다.
| 단계 | 팀의 상태 | 리스크 |
|---|---|---|
| 초기 | 「왠지 실패했지만 재실행했더니 통과됐다」 | 경미 |
| 중기 | 「어차피 Flaky겠지」라고 확인 없이 머지 | ⚠️ 버그 놓침 리스크 |
| 말기 | 「CI가 빨간색이어도 아무도 신경 쓰지 않는다」 | 🔴 자동화의 가치가 제로로 |
Flaky 테스트의 종류와 근본 원인이란?5분류
| 종류 | 전형적인 증상 | 검지 방법 | 주요 발생 도구 | 수정 난이도 |
|---|---|---|---|---|
| ① Wait 계 | 「요소를 찾을 수 없다」「클릭할 수 없다」 | 재실행하면 통과 | Selenium / Playwright | 🟢 비교적 낮음 |
| ② 환경 의존 계 | 「로컬은 통과하지만 CI에서 실패」 | CI 환경에서만 실패 | Selenium / Playwright | 🟡 중간 |
| ③ 데이터 경합 계 | 「병렬 실행 시에만 실패」 | 병렬 실행 시에만 실패 | 전반 | 🔴 높음 |
| ④ 외부 의존 계 | 「외부 API 응답이 느리면 실패」 | 특정 시간대·외부 장애 시에 집중 | API / E2E 테스트 | 🟡 중간 |
| ⑤ 설계 기인 계 | 「테스트 순서를 바꾸면 실패」 | 실행 순서 변경으로 재현 | 전반 | 🔴 높음 |
먼저 확인:증상에서 Flaky 종류를 진단한다면?
| 이런 증상이 있다 | 먼저 의심하는 Flaky 종류 | 대책 STEP |
|---|---|---|
| 재실행하면 통과됐다 | ① Wait 계 | → STEP 2로 |
| 로컬은 통과하지만 CI에서만 실패 | ② 환경 의존 계 | → STEP 3으로 |
| 병렬 실행(pytest-xdist)시에만 실패 | ③ 데이터 경합 계 | → STEP 4로 |
| 특정 시간대·외부 서비스 장애 시에 집중 | ④ 외부 의존 계 | → STEP 5로 |
| 테스트 순서를 바꾸면 재현된다 | ⑤ 설계 기인 계 | → STEP 6으로 |
| 어떤 조건에서도 재현 불가·대책해도 재발 | 격리·삭제를 검토 | → STEP 7로 |
STEP 1:먼저 Flaky를 계측·가시화한다란?
「고치기 전에 먼저 세어본다」——이것이 지옥 탈출의 첫 번째 단계입니다. 감각이 아닌 데이터로 파악함으로써 어디서부터 손을 댈지 우선순위가 정해집니다.
| 지표 | 계측 방법 | 판단 목안 |
|---|---|---|
| Flaky율 | 「실패한 횟수 ÷ 실행한 횟수」 | 5% 이상 요대처(팀 규모·CI 빈도·재실행 운용에 따라 다르지만, 많은 현장에서 이 정도부터 「CI를 신뢰하지 않게 되는」경향이 강해짐) |
| 영향 테스트 수 | CI 실패 로그에서 집계 | 전체 테스트의 10% 초과는 위험 영역 |
| CI 블록 시간 | 재실행이 필요했던 횟수×평균 실행 시간 | 주 1시간 초과는 업무 비용화 |
# ⚠️ pytest 표준 기능이 아닙니다. 사전 설치 필요:pip install pytest-repeat
# ※ CI 편입 시에는 실행 시간 증가 → Flaky 조사 용도로만 사용 권장
pytest tests/test_login.py --count=5
# pytest --junitxml=results.xml 로 결과 저장
# Datadog / Grafana / Allure Report / ReportPortal 등에서 장기 트렌드 확인STEP 2:① Wait 계 Flaky의 대책이란?
가장 많고 비교적 고치기 쉬운 Flaky입니다(SPA나 virtual DOM 환경에서는 난이도가 올라가는 경우도 있습니다).
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
# 주:driver가 초기화되어 있는 것을 전제로 합니다(pytest fixture 또는 setUp()으로 생성)
# ❌ Flaky:time.sleep 고정 대기
import time
time.sleep(3)
driver.find_element(By.ID, "submit").click()
# ✅ 권장:WebDriverWait로 명시적 대기
wait = WebDriverWait(driver, 10)
element = wait.until(EC.element_to_be_clickable((By.ID, "submit")))
element.click()
# ✅ 비동기로 disabled→enabled가 되는 버튼(위의 wait 정의 필요)
wait.until(lambda d: not d.find_element(By.ID, "submit").get_attribute("disabled"))Playwright의 경우
from playwright.sync_api import expect
# Playwright는 click() 전에 visible/stable/enabled 등을 자동 확인하는 auto-waiting 기능을 가집니다
# 단 이것은 「그 순간의 상태 체크」이므로 비동기 상태 변화에는 expect()와의 병용이 필요합니다
# ✅ expect()로 enabled 상태를 확인하고 나서 클릭
expect(page.locator("#submit")).to_be_enabled()
page.locator("#submit").click()
# ✅ API 응답 후에 표시되는 콘텐츠
expect(page.locator(".search-results")).to_be_visible()STEP 3:② 환경 의존 계 Flaky의 대책이란?
| 원인 | 증상 | 대책 |
|---|---|---|
| viewport 차이 | CI에서 클릭 불가 | --window-size=1920,1080 고정 |
| CI 성능 부족 | 타임아웃이 CI에서만 빈발 | CI timeout을 로컬보다 크게 설정 |
| Docker 이미지 차이 | 브라우저 버전 불일치 | Docker 이미지의 Chrome 버전 고정 |
| 타임존 | 날짜·시간 테스트가 CI에서 오동작 | TZ=UTC 를 CI 환경 변수에 설정 |
STEP 4:③ 데이터 경합 계 Flaky의 대책이란?
import pytest
import uuid
# ✅ 대책:테스트마다 유니크한 데이터를 생성한다
@pytest.fixture
def unique_user():
"""테스트마다 유니크한 유저를 생성하고 테스트 후 삭제"""
email = f"test_{uuid.uuid4().hex[:8]}@example.com"
user = create_user(email)
yield user
try:
delete_user(user.id)
except Exception as e:
# cleanup 실패는 테스트 본체에 영향시키지 않는다
# 실무에서는 logger.warning(f"cleanup failed: {e}") 으로 기록 권장
pass
def test_update_user(unique_user):
update_user(unique_user.id, name="Updated")pytest --randomly-seed=random(pytest-randomly 플러그인)으로 테스트 순서를 셔플하면 순서 의존 Flaky가 즉시 발견됩니다.STEP 5:④ 외부 의존 계 Flaky의 대책이란?
from unittest.mock import patch, MagicMock
# ✅ 대책①:외부 API를 목업화한다
@patch("requests.get")
def test_user_profile_mocked(mock_get):
mock_get.return_value = MagicMock(status_code=200, json=lambda: {"id": 1})
response = requests.get("https://api.example.com/users/1")
assert response.status_code == 200
# ✅ 대책②:리트라이(외부 의존이 피할 수 없는 경우만)
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=4))
def call_external_api():
return requests.get("https://api.example.com/users/1", timeout=(3, 10))STEP 6:⑤ 설계 기인 계 Flaky의 대책이란?
import pytest
# ❌ 설계 기인 Flaky:상태 변경을 수반하는 scope="session"(scope="session" 자체가 나쁜 것은 아님)
# 읽기 전용 데이터·immutable한 설정값에서는 안전하게 사용 가능
@pytest.fixture(scope="session")
def logged_in_driver(driver):
driver.get("/login")
driver.find_element(By.ID, "submit").click()
yield driver
# 로그아웃 없음 → 다음 테스트에 상태가 남는다
# ✅ 대책:테스트마다 셋업·클린업을 완결시킨다
@pytest.fixture
def logged_in_driver(driver):
driver.get("/login")
driver.find_element(By.ID, "submit").click()
yield driver
driver.get("/logout") # ← 매번 클린업STEP 7:고칠 수 없는 Flaky는 격리·삭제를 검토한다란?
삭제에 의해 커버리지가 저하되고 리그레션 검지가 약해지는 트레이드오프가 있습니다. 삭제하는 경우는 대상 시나리오를 다른 수단(수동 테스트·단위 테스트)으로 담보하는 것이 조건입니다.
import pytest
# ✅ 방법①:일시적 격리(이유·날짜·이슈 링크를 반드시 기록)
@pytest.mark.skip(reason="Flaky: 원인 조사 중 (2026-05-01 by Yoshi) #issue-123")
def test_payment_flow():
pass
# ✅ 방법②:실패 예정 마크
@pytest.mark.xfail(reason="외부 결제 API 불안정 - #issue-456", strict=False)
def test_external_payment():
pass
# ✅ 방법③:flaky 전용 mark로 메인 CI에서 제외
# pytest.ini: markers = flaky: 불안정한 테스트
@pytest.mark.flaky
def test_unstable_feature():
pass
# pytest -m "not flaky" # 메인 CI
# pytest -m "flaky" # 별도 감시 잡- Slack 통지:flaky 마크 테스트 결과를 전용 채널에 자동 통지
- Flaky 목록 관리:격리 일·담당자·이슈 링크를 스프레드시트나 Jira에서 관리
- 주간 리뷰:3개월 이상 방치된 것은 삭제 판단
Flaky 테스트의 재발 방지:설계 단계에서 할 것
| 설계 규칙 | 내용 | 효과 |
|---|---|---|
| 테스트의 독립성 | 각 테스트는 앞뒤 테스트에 의존하지 않는다 | 🔴 데이터 경합 방지 |
| 유니크 데이터 | 테스트 데이터는 매번 유니크하게 생성 | 🔴 데이터 경합 방지 |
| 외부 의존의 목업화 | 외부 API는 원칙적으로 목업으로 대체 | 🔴 외부 의존 방지 |
| 명시적 대기 | time.sleep 폐지, expect()/WebDriverWait 사용 | 🟢 Wait 계 방지 |
| 환경 통일 | Docker 이미지와 브라우저 버전을 고정 | 🟡 환경 의존 방지 |
| 정기적인 Flaky 계측 | 주간으로 Flaky율 확인·5% 초과하면 즉시 대처 | 전반적인 조기 발견 |
FAQ
Q. 「일단 재실행」은 절대 NG인가요?
NG는 아니지만 응급처치에 불과합니다. 문제는 습관이 된 순간입니다. 재실행 결과를 기록하고 「이번 주 재실행이 필요했던 테스트 목록」을 정기적으로 확인하여 원인 조사에 활용하세요. 「기록하지 않는 재실행」이 지옥의 입구입니다.
Q. Playwright는 Selenium보다 Flaky가 적은가요?
Wait 설계 기인의 Flaky는 줄어듭니다. 단, 데이터 경합·외부 의존·설계 기인의 Flaky는 도구를 바꿔도 해결되지 않습니다. 「Playwright로 이전하면 Flaky 지옥에서 탈출할 수 있다」는 반은 맞는 말입니다.
Q. Flaky 테스트의 대응 우선순위는 어떻게 정하나요?
①Flaky율이 높은 것(5% 이상), ②CI를 블록하는 빈도가 높은 것, ③수정 난이도가 낮은 것(Wait 계)——이 3가지 축으로 스코어를 매겨 우선순위를 정하세요. 낮은 난이도의 Wait 계·환경 의존 계부터 해결하면 「Quick Win」을 얻어 모티베이션이 유지됩니다.
📖 관련 기사
【Flaky가 생기는 원인】
- Selenium 운용 붕괴 이야기 7선|Wait 혼재·ChromeDriver 원인의 Flaky
- Playwright 도입에서 실패한 이야기 7선|auto-waiting 과신의 Flaky
- 자동화하지 않는 것이 좋은 테스트 케이스 7선|무리한 자동화가 Flaky를 만든다
【구현·설계】
- Playwright × pytest 베스트 프랙티스
- Selenium × pytest 실전 가이드|fixture scope 설계
- Page Object Model|유지보수 비용 폭발을 막는 설계 패턴
【로드맵】
먼저 계측하여 가시화하고, 종류를 특정하여 우선순위를 두고 대처한다——이 순서로 진행하는 것이 지옥에서 영속적으로 탈출하기 위한 가장 효율적인 방법입니다.
📋 이 기사의 정리
- Flaky의 진짜 두려움:테스트 결과를 아무도 신뢰하지 않게 된다. 계측·가시화가 첫 번째 단계
- Flaky는 Wait 계·환경 의존 계·데이터 경합 계·외부 의존 계·설계 기인 계의 5종류로 분류 가능
- 수정 난이도:Wait 계≤환경 의존 계≤외부 의존 계<데이터 경합 계≒설계 기인 계. 낮은 난이도부터 착수
- Wait 계:time.sleep 폐지, WebDriverWait/expect()로 상태를 명시적으로 확인
- 데이터 경합 계:유니크 데이터 생성, fixture에서 클린업 보증
- 고쳐지지 않는 Flaky는 격리하고 계속 감시——격리한 채 잊혀지는 것이 최악의 패턴
- 재발 방지 설계 규칙:독립성·유니크 데이터·목업화·환경 통일·정기 계측
