AI 코딩 에이전트(Claude Code, Codex, Cursor)가 리팩토링에도 살아남는 테스트를 짜게 만드는 드롭인 규칙 파일.
AI한테 "테스트 짜줘" 하면 매번 같은 모양이 나옵니다. 모든 의존성을 모킹하고, toHaveBeenCalledWith(...)로 내부 호출을 검증하고, 커버리지 100%. 근데 내부 구현 조금만 바꾸면 — 행동은 그대로인데 — 테스트 절반이 빨갛게 뜹니다. 전형적인 coverage theater.
이유는 단순합니다. LLM은 기본적으로 런던파(Mockist) TDD 스타일로 테스트를 짜는 편향이 있습니다. 코드가 자주 바뀌는 환경에서 가장 빨리 깨지는 스타일이죠.
반면 OpenAI가 실제로 Codex를 테스트하는 방식을 보면 (Rust 테스트 521개), 내부 trait 모킹은 0건. HTTP 경계에서만 wiremock으로 모킹합니다. 전형적인 고전파(Classist) 스타일.
이 파일은 그 접근법을 규칙으로 정리해서, 에이전트가 매 세션마다 읽도록 만듭니다.
AGENTS.md를 레포의 원하는 경로에 두세요.
- 모킹 경계 — 뭘 모킹해야 하고, 뭘 절대 모킹하면 안 되는지
- Assertion 스타일 — 호출 순서가 아니라 상태/반환값 검증
- 테스트 네이밍 — 행동 기반 템플릿
- 테스트 피라미드 — 레이어별 예산, 언제 생략할지
- 도메인 엔티티 추출 — 언제, 왜
- Property-Based Testing — example 테스트로 부족할 때
- Flaky 테스트 처리 — 격리 정책, 근본 원인 규칙
- 점진 마이그레이션 — 기존 Mockist 코드베이스에 점진 도입
- PR Red Flags — 리뷰에서 반려해야 할 신호
총 145줄. 언어/프레임워크 비종속.
# Before — Mockist 기본값 (AI가 시키지 않으면 짜는 스타일)
expect(prisma.user.findUnique).toHaveBeenCalledWith({ where: { id } })
expect(prisma.discount.findFirst).toHaveBeenCalled()
expect(cache.get).toHaveBeenCalledWith("user:123")
# After — Classist (이 파일이 만들어내는 스타일)
const result = await service.calculateDiscount(userId, productId)
expect(result.finalPrice).toBe(8000)
두 번째 테스트는 가격 계산 행동만 보존되면 어떤 리팩토링에도 깨지지 않습니다.
첫 번째는 findUnique를 findFirst로 바꾸는 순간 무너집니다.
다음 자료들을 기반으로 정리:
- 2025–2026년 AI 테스팅 관련 논문/아티클 ~20개
- OpenAI Codex OSS 테스트 코드 분석 (
codex-rs/) - IEEE TSE 2026, arXiv 2604.06373, Control Plane, Codemanship, METR RCT, Galileo 등
테스트에게 구현을 숨기고, 구현에게 테스트를 숨겨라. 둘을 잇는 건 오직 행동이다.
MIT