
테스트를 먼저 작성하여 Claude Code의 폭주를 막는 법 — TDD로 사양을 고정하는 실천 루프
요약
Claude Code 사용 시 발생하는 모호한 요구사항과 구현 오류를 방지하기 위해 TDD(테스트 주도 개발)를 활용하는 방법을 소개합니다. 테스트 코드를 사양으로 고정하여 Claude가 스스로 검증하고 구현할 수 있는 실천적인 루프와 프롬프트 전략을 다룹니다.
핵심 포인트
- 자연어 지시의 모호함을 테스트 코드로 해결
- 테스트를 통해 Claude의 자기 검증 루프 형성
- 테스트 파일 동결을 통한 사양 침식 방지
- TDD를 활용한 안전한 리팩토링 및 기능 추가
Claude Code에게 기능을 통째로 구현하게 할 때, 이런 경험 없으신가요?
- 처음에는 괜찮아 보였는데, 자세히 보니 요구사항의 절반밖에 충족하지 못함
- "덤으로" 상관없는 파일까지 수정해버림
- 수정을 요청하면, 분명 고쳤던 다른 부분이 망가짐
- "동작합니다!"라고 말하지만, 실제로 실행하면 에러가 나며 종료됨
- 5번 정도 대화를 주고받으면, 이제 무엇이 올바른 동작인지 스스로도 알 수 없게 됨
이것은 모델이 멍청해서가 아닙니다. "정답(사양)이 언어로만 정의되어 있기" 때문입니다.
자연어 사양은 모호하며 해석의 폭이 넓습니다. Claude Code는 그 폭 안에서 "그럴듯한 것"을 생성하고, 당신은 출력을 보고 "아니, 그게 아니라"라며 말로 덧붙입니다. 이 과정이 반복되면 사양은 점점 어긋나게 됩니다.
이를 막는 가장 심플한 방법이 **TDD(Test-Driven Development, 테스트 주도 개발)**입니다. 테스트를 먼저 작성하게 하면, 그것이 기계가 검증할 수 있는 사양의 고정이 됩니다. Claude Code는 더 이상 "그럴듯한 것"만으로는 합격할 수 없으며, 테스트가 초록색(Green)이 될 때까지 구현을 계속할 수밖에 없게 됩니다.
이 기사에서는 Claude Code로 TDD 루프(테스트 → 구현 → 리팩토링)를 실제로 돌리는 절차를, 복사해서 사용할 수 있는 프롬프트와 함께 소개합니다. 모두 오늘 세션부터 바로 시도해 볼 수 있습니다.
먼저 원리만 파악해 둡시다. 이 부분이 납득되면 이후의 절차는 자연스럽게 지킬 수 있습니다.
자연어 지시와 테스트 코드의 결정적인 차이는 **"합격 여부를 누가 판정하는가"**입니다.
| 사양의 형태 | 합격 판정자 | 일어나는 일 |
|---|---|---|
| 자연어 지시만 있음 | 당신 (인간이 육안으로 확인) | 해석이 어긋남 · 놓침 · 왕복 횟수 증가 |
| 먼저 작성한 테스트 | 테스트 러너 (기계) | 초록/빨강으로 명확함 · Claude가 스스로 확인 가능 |
테스트를 먼저 작성하면 다음 세 가지를 동시에 얻을 수 있습니다.
- 사양이 고정됨 — "올바른 동작"이 코드로 하나로 정해집니다. 나중에 "역시 이렇게 해줘"라고 말로 흔들어도, 테스트를 바꾸지 않는 한 기준은 흔들리지 않습니다.
- Claude가 자기 검증을 할 수 있음 — 구현을 하면 스스로
npm test를 실행하여, 빨간색(Red)이라면 수정합니다. 당신이 육안으로 리뷰하기 전에 Claude 스스로 최소한의 라인을 보장합니다. - 수정이 안전해짐 — 리팩토링이나 기능 추가로 기존 동작을 망가뜨리면, 테스트가 빨간색이 되어 즉시 들통납니다. "고쳤더니 다른 게 망가졌다"는 무한 루프에서 벗어날 수 있습니다.
즉, 테스트는 Claude Code에게 있어 **"넘어서는 안 될 울타리"**입니다. 울타리가 없으면 자유롭게 뛰어다니다 길을 잃지만
방금 확정한 tests/calcTax.test.ts를 통과하는 구현을 작성해 주세요.
【제약 사항】
- 테스트를 통과하기 위한 최소한의 구현에 그칠 것
...
여기서 핵심이 되는 것이 **"테스트 파일은 변경하지 마라"**라는 문구입니다. 이 문구가 없으면, Claude는 구현이 테스트를 통과하지 못할 때 "테스트를 구현에 맞춰서 다시 쓰기"라는 최악의 수를 둡니다. 사양 (Specification)이 구현에 침식되는 순간입니다. 테스트를 동결함으로써, Claude는 "구현을 고치는" 것 이외의 도망갈 길을 잃게 됩니다.
그리고 "모든 테스트가 초록색(Green)이 될 때까지 고칠 것". 이를 통해 Claude는 스스로 테스트를 실행하고, 빨간색(Red)이 있으면 스스로 수정하는 루프에 진입합니다. 당신이 눈으로 확인하기 전에, 기계적인 합격 라인은 Claude 스스로가 담보해 줍니다.
테스트가 초록색이 되면 코드를 정리합니다. 여기서도 테스트가 지켜줍니다.
calcTax.ts를 리팩터링(Refactor)해 주세요.
【제약 사항】
- 동작(Behavior)은 일절 바꾸지 말 것
...
리팩터링은 "동작을 바꾸지 않고 구조를 개선하는" 작업입니다. 테스트가 초록색인 상태라면 동작이 유지되고 있다는 보장이 있기에 안심하고 구조를 만질 수 있습니다. 테스트 없는 리팩터링은 단순한 코드 재작성일 뿐이며, 퇴보(Regression)가 두려워 선뜻 나설 수 없습니다.
"로그인 실패 5회 시 계정 잠금" 기능을 예로 들어, TDD가 없는 경우와 있는 경우를 비교해 보겠습니다.
로그인 실패가 5회 연속되면 계정을 잠그는 기능을 만들어줘
돌아오는 구현은 언뜻 보기에는 그럴싸하게 동작합니다. 하지만 이런 허점들이 나중에 발견되곤 합니다.
- "5회째에 잠금"인지 "5회 실패한 다음(6회째)에 잠금"인지 모호함
- 로그인 성공 시 실패 횟수가 리셋되는지 불분명함
- 잠금 이후의 동작(해제 조건, 에러 메시지)이 미정의됨
- 이를 지적할 때마다 말로 주고받아야 함 → 수정하면 다른 조건이 깨짐
사양이 말로만 되어 있기 때문에 경계(Boundary)가 모두 모호합니다. Claude는 모호한 부분을 마음대로 채워 넣고, 당신은 출력을 보고 나서야 "그게 아니야"라고 깨닫게 됩니다. 사후 수정 전쟁이 벌어지는 것입니다.
로그인 실패 잠금 기능을 만듭니다. 우선 Red 페이즈(Red Phase). 테스트만 작성해 주세요.
구현은 작성하지 마세요.
【사양 = 테스트로 고정하고 싶은 경계】
...
테스트를 작성하게 한 시점에서, 모호했던 경계가 모두 코드상의 수치와 분기(Branch)로 확정됩니다. "정확히 5회", "4회까지는 OK", "성공 시 리셋"이 각각 독립된 테스트 케이스가 됩니다. 이 상태에서 구현을 요청하면, Claude는 이 경계를 단 하나도 벗어날 수 없습니다. 벗어나면 테스트가 빨간색이 되기 때문입니다.
| 관점 | Before (말뿐인 경우) | After (테스트 선행) |
|---|---|---|
| 경계 조건 | 구현 후에 발견 | 착수 전에 확정 |
| ... | ... |
After의 가장 큰 장점은 리뷰 대상이 "구현 전체"에서 "테스트만"으로 바뀐다는 것입니다. 테스트만 올바르다면 구현이 초록색이 된 시점에서 사양은 충족된 것입니다. 읽어야 할 양이 급격히 줄어듭니다.
매번 프롬프트로 "테스트부터 작성해"라고 지시하는 것은 번거롭습니다. 프로젝트의 CLAUDE.md에 방침을 적어두면, Claude가 표준으로 TDD 순서를 지키게 됩니다.
## 구현 진행 방식
기능 추가 및 버그 수정은 원칙적으로 테스트 퍼스트(Test-first)로 진행한다.
1. 먼저 실패하는 테스트를 작성하고, 테스트가 빨간색임을 확인한 뒤 멈춘다
...
포인트는 **"테스트가 빨간색인 시점에서 일단 멈춘다"**를 명문화하는 것입니다. 이렇게 하면 스텝 1과 스텝 2 사이에 인간의 리뷰가 반드시 끼어들게 되어, 사양의 타당성 체크가 누락되지 않습니다.
CLAUDE.md의 세부 설계나 Hooks를 통한 자동화는 본 기사의 범위를 벗어나지만, "TDD를 표준 동작으로 만드는 한 문장"만이라도 넣어두면 매번 내리는 지시가 한결 편해집니다.
Claude Code로 TDD를 돌릴 때, 특정 실패 패턴에 반드시 부딪히게 됩니다. 미리 알고 있으면 피할 수 있습니다.
가장 많은 실패 사례입니다. "테스트 작성하고 구현해줘"라고 한 번에 요청하면, Claude는 테스트를 구현에 유리하게 작성합니다. 구현이 틀렸더라도 그 틀린 구현에 맞춘 테스트를 작성하기 때문에 둘 다 초록색이 되어 버그가 그대로 남게 됩니다.
회피책: Red 페이즈(테스트만)와 Green 페이즈(구현)를 반드시 분리하고, 그 사이에 인간의 리뷰를 끼워 넣으세요. "구현은 작성하지 마라"를 매번 명시해야 합니다.
TDD의 효과는 **"테스트가 올바른 사양이다"**라는 전제가 있어야 합니다. 테스트가 틀려 있다면, Claude는 틀린 사양을 완벽하게 구현할 것입니다. 이는 TDD가 없는 것보다 더 위험합니다. 틀린 내용이 "초록색"이라는 인증을 받기 때문입니다.
회피책: Red 페이즈 (Red phase)에서 생성된 테스트는 반드시 인간이 읽어야 합니다. 특히 "기대값 (expect)이 정말로 올바른가"를 케이스별로 하나씩 확인하세요. 이 단계만큼은 절대로 생략하지 마세요. 테스트를 읽는 시간이 구현 전체를 읽는 시간보다 훨씬 짧다는 점을 기억하십시오.
구현이 도저히 통과되지 않을 때, Claude는 "테스트를 구현에 맞춰 수정했습니다"라고 하는 경우가 있습니다. 이는 사양이 구현에 패배한 순간입니다.
회피책: "테스트 파일은 변경하지 않는다"를 제약 조건으로 반드시 넣으세요. 테스트를 바꿀 필요가 정말로 있다면, 그것은 사양 변경이므로 인간이 판단하도록 명확히 분리합니다.
Claude는 구현 후에 "모든 테스트를 통과했습니다"라고 보고할 때가 있지만, 실제로는 명령어를 실행하지 않았을 수도 있습니다. 생성만 해두고 "통과할 것이다"라고 추측하여 보고하는 케이스입니다.
회피책: "반드시 npm test를 실행하고, 그 출력 결과(초록색 건수)를 붙여넣어 보고해"라고 지시하세요. 실행 로그를 보여달라고 하면 추측성 보고를 걸러낼 수 있습니다.
하나의 테스트로 모든 것을 검증하려고 하면
우선은 무료 리포지토리 (Repository)부터 시도해 보시고, 더 체계적으로 사용하고 싶어지면 그때 검토해 보셔도 충분합니다. 기사의 내용만으로도 효과를 볼 수 있습니다.
최신 팁은 X에서도 발신하고 있습니다: @k___n___t_1125
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기