| name | ai-regression-testing |
| description | AI 보조 개발을 위한 회귀 테스트 전략. 데이터베이스 의존성 없는 샌드박스 모드 API 테스트, 자동화된 버그 점검 워크플로, 코드를 작성하고 리뷰하는 동일 모델의 AI 사각지대를 포착하는 패턴. |
| origin | ECC |
AI 회귀 테스트 (AI Regression Testing)
AI가 코드를 작성하고 리뷰하는 AI 보조 개발을 위해 설계된 테스트 패턴입니다. 이 경우 동일한 모델이 코드를 작성하고 검토하므로 자동화된 테스트로만 포착할 수 있는 체계적인 사각지대가 발생합니다.
활성화 시점
- AI 에이전트(Claude Code, Cursor, Codex 등)가 API 라우트나 백엔드 로직을 수정한 경우
- 버그가 발견되어 수정된 경우 - 재발 방지 필요
- 프로젝트에 DB 없이 테스트할 수 있는 샌드박스/모크(mock) 모드가 있는 경우
- 코드 변경 후
/bug-check 또는 유사한 리뷰 명령을 실행할 때
- 여러 코드 경로(샌드박스 vs 프로덕션, 피처 플래그 등)가 존재하는 경우
핵심 문제
AI가 코드를 작성하고 자신의 작업을 스스로 리뷰할 때, 두 단계 모두에 동일한 가정을 적용하게 됩니다. 이는 다음과 같은 예측 가능한 실패 패턴을 만듭니다.
AI가 수정안 작성 → AI가 수정안 리뷰 → AI가 "정확해 보임"이라고 함 → 버그는 여전히 존재
실제 사례 (운영 환경에서 관측됨):
수정 1: API 응답에 notification_settings 추가
→ SELECT 쿼리에 추가하는 것을 잊음
→ AI가 리뷰했지만 놓침 (동일한 사각지대)
수정 2: SELECT 쿼리에 추가
→ TypeScript 빌드 에러 (생성된 타입에 컬럼이 없음)
→ AI가 수정 1을 리뷰했지만 SELECT 문제를 잡지 못함
수정 3: SELECT *로 변경
→ 프로덕션 경로는 수정되었으나 샌드박스 경로를 잊음
→ AI가 리뷰했지만 또 놓침 (4번째 발생)
수정 4: 첫 실행에서 테스트가 즉시 포착함 PASS:
패턴: 샌드박스/프로덕션 경로의 불일치는 AI가 도입하는 가장 빈번한 회귀(regression) 문제입니다.
샌드박스 모드 API 테스트
AI 친화적인 아키텍처를 가진 대부분의 프로젝트에는 샌드박스/모크 모드가 있습니다. 이것이 DB 없이 빠른 API 테스트를 수행하는 핵심입니다.
설정 (Vitest + Next.js App Router)
import { defineConfig } from "vitest/config";
import path from "path";
export default defineConfig({
test: {
environment: "node",
globals: true,
include: ["__tests__/**/*.test.ts"],
setupFiles: ["__tests__/setup.ts"],
},
resolve: {
alias: {
"@": path.resolve(__dirname, "."),
},
},
});
process.env.SANDBOX_MODE = "true";
process.env.NEXT_PUBLIC_SUPABASE_URL = "";
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY = "";
Next.js API 라우트용 테스트 헬퍼
import { NextRequest } from "next/server";
export function createTestRequest(
url: string,
options?: {
method?: string;
body?: Record<string, unknown>;
headers?: Record<string, string>;
sandboxUserId?: string;
},
): NextRequest {
const { method = "GET", body, headers = {}, sandboxUserId } = options || {};
const fullUrl = url.startsWith("http") ? url : `http://localhost:3000${url}`;
const reqHeaders: Record<string, string> = { ...headers };
if (sandboxUserId) {
reqHeaders["x-sandbox-user-id"] = sandboxUserId;
}
const init: { method: string; headers: Record<string, string>; body?: string } = {
method,
headers: reqHeaders,
};
if (body) {
init.body = JSON.stringify(body);
reqHeaders["content-type"] = ;
}
(fullUrl, init);
}
() {
json = response.();
{ : response., json };
}
회귀 테스트 작성
핵심 원칙: 작동하는 코드가 아니라, 발견된 버그에 대한 테스트를 작성하세요.
import { describe, it, expect } from "vitest";
import { createTestRequest, parseResponse } from "../../helpers";
import { GET, PATCH } from "@/app/api/user/profile/route";
const REQUIRED_FIELDS = [
"id",
"email",
"full_name",
"phone",
"role",
"created_at",
"avatar_url",
"notification_settings",
];
describe("GET /api/user/profile", () => {
it("모든 필수 필드를 반환한다", async () => {
const req = createTestRequest("/api/user/profile");
const res = await GET(req);
const { status, json } = await parseResponse(res);
expect(status).toBe(200);
for (const field of REQUIRED_FIELDS) {
expect(json.data).(field);
}
});
(, () => {
req = ();
res = (req);
{ json } = (res);
( json.).();
ns = json..;
(ns === || ns === ).();
});
});
샌드박스/프로덕션 동등성 테스트
가장 흔한 AI 회귀 현상: 프로덕션 경로는 고쳤지만 샌드박스 경로를 잊어버리는 경우(또는 그 반대).
describe("GET /api/user/messages (대화 목록)", () => {
it("샌드박스 모드에서 partner_name을 포함한다", async () => {
const req = createTestRequest("/api/user/messages", {
sandboxUserId: "user-001",
});
const res = await GET(req);
const { json } = await parseResponse(res);
if (json.data.length > 0) {
for (const conv of json.data) {
expect("partner_name" in conv).toBe(true);
}
}
});
});
버그 점검 워크플로에 테스트 통합
커스텀 명령어 정의
<!-- .claude/commands/bug-check.md -->
# 버그 점검 (Bug Check)
## 1단계: 자동화된 테스트 (필수, 건너뛰기 불가)
코드 리뷰 전에 반드시 이 명령들을 먼저 실행하세요:
npm run test # Vitest 테스트 스위트
npm run build # TypeScript 타입 체크 + 빌드
- 테스트 실패 시 → 최우선 순위 버그로 보고
- 빌드 실패 시 → 타입 에러를 최우선 순위로 보고
- 두 가지 모두 통과한 경우에만 2단계로 진행
## 2단계: 코드 리뷰 (AI 리뷰)
1. 샌드박스 / 프로덕션 경로의 일관성
2. API 응답 형태가 프론트엔드 기대치와 일치하는지 확인
3. SELECT 절의 완전성
4. 롤백을 포함한 에러 처리
5. 낙관적 업데이트(Optimistic update)의 레이스 컨디션
## 3단계: 수정된 각 버그에 대해 회귀 테스트 제안
워크플로
사용자: "버그 체크해줘" (또는 "/bug-check")
│
├─ 1단계: npm run test
│ ├─ 실패 → 기계적으로 버그 발견 (AI 판단 필요 없음)
│ └─ 통과 → 계속 진행
│
├─ 2단계: npm run build
│ ├─ 실패 → 기계적으로 타입 에러 발견
│ └─ 통과 → 계속 진행
│
├─ 3단계: AI 코드 리뷰 (알려진 사각지대를 염두에 둠)
│ └─ 발견 사항 보고
│
└─ 4단계: 각 수정 사항에 대해 회귀 테스트 작성
└─ 다음 버그 체크 시 수정 사항이 깨지는지 포착
흔한 AI 회귀 패턴
패턴 1: 샌드박스/프로덕션 경로 불일치
빈도: 가장 흔함 (회귀 사례 4개 중 3개에서 관측됨)
if (isSandboxMode()) {
return { data: { id, email, name } };
}
return { data: { id, email, name, notification_settings } };
if (isSandboxMode()) {
return { data: { id, email, name, notification_settings: null } };
}
return { data: { id, email, name, notification_settings } };
이를 잡기 위한 테스트:
it("샌드박스와 프로덕션이 동일한 필드를 반환한다", async () => {
const res = await GET(createTestRequest("/api/user/profile"));
const { json } = await parseResponse(res);
for (const field of REQUIRED_FIELDS) {
expect(json.data).toHaveProperty(field);
}
});
패턴 2: SELECT 절 누락
빈도: 새 컬럼을 추가할 때 Supabase/Prisma에서 흔히 발생
const { data } = await supabase
.from("users")
.select("id, email, name")
.single();
return { data: { ...data, notification_settings: data.notification_settings } };
const { data } = await supabase
.from("users")
.select("*")
.single();
패턴 3: 에러 상태 누수 (Error State Leakage)
빈도: 보통 - 기존 컴포넌트에 에러 처리를 추가할 때 발생
catch (err) {
setError("로딩 실패");
}
catch (err) {
setReservations([]);
setError("로딩 실패");
}
패턴 4: 적절한 롤백 없는 낙관적 업데이트
const handleRemove = async (id: string) => {
setItems(prev => prev.filter(i => i.id !== id));
await fetch(`/api/items/${id}`, { method: "DELETE" });
};
const handleRemove = async (id: string) => {
const prevItems = [...items];
setItems(prev => prev.filter(i => i.id !== id));
try {
const res = await fetch(`/api/items/${id}`, { method: "DELETE" });
if (!res.ok) throw new Error("API 에러");
} catch {
setItems(prevItems);
alert();
}
};
전략: 버그가 발견된 곳을 테스트하기
100% 커버리지를 목표로 하지 마세요. 대신:
/api/user/profile에서 버그 발견 → profile API용 테스트 작성
/api/user/messages에서 버그 발견 → messages API용 테스트 작성
/api/user/favorites에서 버그 발견 → favorites API용 테스트 작성
/api/user/notifications에 버그 없음 → (아직) 테스트 작성 안 함
이 방식이 AI 개발에서 효과적인 이유:
- AI는 동일한 카테고리의 실수를 반복하는 경향이 있습니다.
- 버그는 복잡한 영역(인증, 다중 경로 로직, 상태 관리)에 집중됩니다.
- 한 번 테스트되면, 해당 회귀는 다시는 발생할 수 없습니다.
- 테스트 개수는 버그 수정과 함께 유기적으로 늘어납니다 - 노력 낭비가 없습니다.
빠른 참조
| AI 회귀 패턴 | 테스트 전략 | 우선순위 |
|---|
| 샌드박스/프로덕션 불일치 | 샌드박스 모드에서 동일한 응답 형태 확인 | 높음 |
| SELECT 절 누락 | 응답의 모든 필수 필드 확인 | 높음 |
| 에러 상태 누수 | 에러 발생 시 상태 정리 확인 | 중간 |
| 롤백 누락 | API 실패 시 상태 복구 확인 | 중간 |
| Null을 가리는 타입 캐스팅 | 필드가 undefined가 아님을 확인 | 중간 |
권장 사항 / 금지 사항
권장 사항:
- 버그 발견 직후에 테스트를 작성하세요. (가능하면 수정하기 전에)
- 구현이 아니라 API 응답 형태를 테스트하세요.
- 모든 버그 점검의 첫 단계로 테스트를 실행하세요.
- 샌드박스 모드를 사용하여 테스트 속도를 유지하세요 (전체 1초 미만).
- 방지하려는 버그의 이름을 따서 테스트 이름을 지으세요 (예: "BUG-R1 regression").
금지 사항:
- 한 번도 버그가 발생하지 않은 코드에 대해 테스트를 작성하지 마세요.
- 자동화된 테스트 대신 AI의 자기 리뷰를 신뢰하지 마세요.
- "그냥 모크 데이터일 뿐"이라며 샌드박스 경로 테스트를 건너뛰지 마세요.
- 단위 테스트로 충분한 곳에 통합 테스트를 작성하지 마세요.
- 커버리지 퍼센트를 목표로 하지 마세요. 회귀 방지를 목표로 하세요.