테스트 로트(Test Rot) 문제의 근본 원인과 AI 기반 셀프 힐링, 셀렉터 자동 재바인딩, 테스트 코드 리팩터링, 중복 테스트 감지, 커버리지 갭 분석 등 유지보수 비용 절감 전략을 다룹니다.
Test Rot(테스트 부패)는 시간이 지남에 따라 테스트 스위트가 점진적으로 가치를 잃어가는 현상입니다. 코드는 계속 변화하는데 테스트는 그 속도를 따라가지 못하면서 발생합니다.
대규모 프로젝트에서 테스트 로트의 영향을 수치로 보면 다음과 같습니다.
| 지표 | 건강한 스위트 | 부패된 스위트 |
|---|---|---|
| CI 통과율 | 98%+ | 70-80% |
| skip된 테스트 비율 | 2% 미만 | 15-30% |
| 평균 수정 시간 | 5분 | 30분+ |
| 개발자 신뢰도 | "테스트 실패 = 실제 결함" | "또 플레이키 테스트" |
| 배포 자신감 | 높음 | 낮음 |
테스트 로트는 갑자기 발생하지 않습니다. 매일 조금씩 악화되기 때문에, 문제를 인식했을 때는 이미 상당히 진행된 상태입니다. 정기적인 테스트 건강도 점검이 필수적입니다.
| 원인 | 빈도 | AI 자동화 가능성 |
|---|---|---|
| UI 셀렉터 변경 | 매우 높음 | 높음 (셀프 힐링) |
| API 인터페이스 변경 | 높음 | 중간 (스키마 분석) |
| 테스트 데이터 오염 | 높음 | 중간 (격리 자동화) |
| 비동기 타이밍 변화 | 중간 | 높음 (대기 조건 최적화) |
| 환경 의존성 | 중간 | 중간 (환경 추상화) |
| 비즈니스 로직 변경 | 낮음 | 낮음 (사람 판단 필요) |
4장에서 간략히 다룬 셀프 힐링을 유지보수 관점에서 깊이 살펴보겠습니다.
AI 셀프 힐링의 핵심은 하나의 요소에 대해 여러 개의 셀렉터를 유지하는 것입니다. 주 셀렉터가 실패하면 대체 셀렉터로 자동 전환합니다.
interface HealableLocator {
primary: string;
alternatives: Array<{
strategy: "css" | "xpath" | "text" | "role" | "testid";
selector: string;
confidence: number;
}>;
lastHealed?: {
date: string;
from: string;
to: string;
reason: string;
};
}
const loginButton: HealableLocator = {
primary: "[data-testid='login-submit']",
alternatives: [
{ strategy: "role", selector: "button[name='로그인']", confidence: 0.95 },
{ strategy: "text", selector: "text='로그인'", confidence: 0.85 },
{ strategy: "css", selector: ".login-form button[type='submit']", confidence: 0.75 },
{ strategy: "xpath", selector: "//form[@class='login-form']//button", confidence: 0.60 },
],
};셀프 힐링이 임시 복구라면, Selector Re-binding(셀렉터 재바인딩)은 영구적인 수정입니다. AI가 깨진 셀렉터를 감지하면 새로운 셀렉터로 테스트 코드를 업데이트하고, PR을 생성합니다.
interface RebindResult {
testFile: string;
line: number;
oldSelector: string;
newSelector: string;
confidence: number;
reason: string;
}
// AI가 생성한 재바인딩 보고서 예시
const rebindReport: RebindResult[] = [
{
testFile: "e2e/login.spec.ts",
line: 25,
oldSelector: "#login-btn",
newSelector: "[data-testid='login-submit']",
confidence: 0.95,
reason: "ID가 제거되고 data-testid가 추가됨",
},
{
testFile: "e2e/checkout.spec.ts",
line: 42,
oldSelector: ".cart-item:nth-child(1) .remove",
newSelector: "[data-testid='cart-item-0'] [data-action='remove']",
confidence: 0.88,
reason: "CSS 클래스 구조 변경, data 속성 추가됨",
},
];테스트 스위트가 커지면 동일하거나 유사한 경로를 반복 테스트하는 중복이 발생합니다. 중복 테스트는 실행 시간을 늘리고 유지보수 비용을 증가시킵니다.
AI는 테스트 코드의 의미적 유사성을 분석하여 중복을 감지합니다. 단순한 코드 비교가 아니라, 실제로 검증하는 동작이 동일한지를 판단합니다.
=== 중복 테스트 감지 보고서 ===
그룹 1: 로그인 폼 유효성 검증 (유사도 92%)
- auth.spec.ts:15 "이메일 형식 검증"
- login-form.spec.ts:28 "잘못된 이메일 입력 시 에러 표시"
권장: login-form.spec.ts의 테스트로 통합
그룹 2: 장바구니 추가 (유사도 87%)
- cart.spec.ts:40 "상품을 장바구니에 추가"
- product-detail.spec.ts:55 "상품 상세에서 장바구니 추가"
- e2e/shopping-flow.spec.ts:20 "쇼핑 플로우 - 장바구니 추가 단계"
권장: e2e 테스트에 이미 포함되므로, 단위 수준 테스트는 cart.spec.ts로 통합
감지된 중복: 12개 그룹, 34개 테스트
예상 실행 시간 절감: 3분 40초 (전체의 18%)중복을 감지한 후에는 테스트 코드를 리팩터링합니다. AI는 공통 패턴을 추출하여 헬퍼 함수나 페이지 객체로 정리하는 것을 제안합니다.
// auth.spec.ts
test("로그인 성공", async ({ page }) => {
await page.goto("/login");
await page.fill("[data-testid='email']", "user@test.com");
await page.fill("[data-testid='password']", "password123");
await page.click("[data-testid='submit']");
await expect(page).toHaveURL("/dashboard");
});
// profile.spec.ts
test("프로필 수정 전 로그인", async ({ page }) => {
await page.goto("/login");
await page.fill("[data-testid='email']", "user@test.com");
await page.fill("[data-testid='password']", "password123");
await page.click("[data-testid='submit']");
await expect(page).toHaveURL("/dashboard");
// ... 프로필 수정 로직
});// helpers/auth.ts
export async function loginAs(
page: Page,
email: string = "user@test.com",
password: string = "password123"
): Promise<void> {
await page.goto("/login");
await page.getByLabel("이메일").fill(email);
await page.getByLabel("비밀번호").fill(password);
await page.getByRole("button", { name: "로그인" }).click();
await expect(page).toHaveURL("/dashboard");
}
// auth.spec.ts
test("로그인 성공", async ({ page }) => {
await loginAs(page);
});
// profile.spec.ts
test("프로필 수정 전 로그인", async ({ page }) => {
await loginAs(page);
// ... 프로필 수정 로직
});테스트 리팩터링에서 가장 효과적인 패턴은 Page Object Model(POM)입니다. AI가 테스트 코드에서 반복되는 페이지 상호작용을 감지하면, 해당 페이지의 POM 클래스를 자동으로 생성할 수 있습니다. 이는 유지보수성을 크게 향상시킵니다.
Coverage Gap Analysis(커버리지 갭 분석)는 테스트되지 않은 코드 영역을 체계적으로 파악하는 과정입니다. AI는 단순한 라인 커버리지를 넘어, 비즈니스 관점에서 중요한 미검증 시나리오를 찾아냅니다.
=== AI 커버리지 갭 분석 ===
[위험도: 높음] 결제 처리 모듈
파일: src/services/payment.ts
라인 커버리지: 65%
미검증 영역:
- 45-52행: 결제 타임아웃 재시도 로직
- 78-95행: 부분 환불 처리
- 102-110행: 통화 변환 반올림
위험 요인: 최근 3번 변경됨, 결함 이력 2건
권장: 즉시 테스트 추가
[위험도: 중간] 사용자 권한 검사
파일: src/middleware/auth.ts
라인 커버리지: 80%
미검증 영역:
- 30-38행: 만료된 토큰 갱신 로직
- 55-60행: 다중 역할 사용자 처리
위험 요인: 보안 관련 코드
권장: 이번 스프린트 내 테스트 추가
[위험도: 낮음] 로깅 유틸리티
파일: src/utils/logger.ts
라인 커버리지: 45%
미검증 영역:
- 대부분의 로그 포맷팅 로직
위험 요인: 낮음 (부수효과 없음)
권장: 우선순위 낮음AI 기반 테스트 유지보수의 효과를 정량적으로 측정하는 것이 중요합니다.
| 지표 | 측정 방법 | 목표 |
|---|---|---|
| MTTR(Mean Time To Repair) | 테스트 실패 감지에서 수정까지의 평균 시간 | 1시간 미만 |
| Flaky Rate | 전체 테스트 중 플레이키 테스트의 비율 | 2% 미만 |
| Skip Rate | skip 처리된 테스트의 비율 | 1% 미만 |
| Heal Rate | AI가 자동 복구한 테스트의 비율 | 측정 및 추적 |
| Test-to-Code Ratio | 코드 대비 테스트 코드의 비율 | 1:1 ~ 1.5:1 |
interface TestHealthMetrics {
period: string;
totalTests: number;
passRate: number;
flakyRate: number;
skipRate: number;
autoHealedCount: number;
mttrMinutes: number;
duplicatesDetected: number;
coverageGaps: number;
}
// 주간 보고 예시
const weeklyReport: TestHealthMetrics = {
period: "2026-W13",
totalTests: 1247,
passRate: 97.8,
flakyRate: 1.2,
skipRate: 0.5,
autoHealedCount: 8,
mttrMinutes: 35,
duplicatesDetected: 3,
coverageGaps: 5,
};테스트 건강도 지표는 스프린트 회고에서 정기적으로 검토하는 것이 좋습니다. 지표가 악화되는 추세를 조기에 발견하면, 테스트 로트가 심각해지기 전에 대응할 수 있습니다.
이 장에서는 테스트 유지보수의 핵심 과제와 AI 기반 자동화 전략을 살펴보았습니다.
핵심 내용을 정리하면 다음과 같습니다.
8장에서는 AI QA 파이프라인 구축을 다룹니다. 변경 영향 분석 기반 테스트 선택, 위험 기반 우선순위 지정, 플레이키 테스트 자동 격리, 결함 예측, 그리고 GitHub Actions/GitLab CI와의 통합을 실습합니다.
이 글이 도움이 되셨나요?
관련 주제 더 보기
변경 영향 분석 기반 테스트 선택, 위험 기반 우선순위, 플레이키 테스트 자동 격리, 병렬 실행 최적화, 결함 예측, GitHub Actions/GitLab CI 통합을 다루는 AI QA 파이프라인 구축 가이드입니다.
픽셀 비교의 한계를 넘어 Visual AI 기반 시각적 회귀 테스트를 다룹니다. Applitools Eyes, Percy, Chromatic 비교 분석과 동적 콘텐츠 처리, 반응형 레이아웃 테스트, 스토리북 통합을 안내합니다.
유저 스토리에서 Gherkin 시나리오를 거쳐 실행 가능한 테스트로 자동 변환하는 Agentic QA의 아키텍처, 자율 탐색 테스트, Human-on-the-loop 감독 체계, 그리고 품질 게이트 통합을 다룹니다.