먼저 무엇을 원하는지 말하라. 그다음에 그것을 만들어라.
사용자가 마주하는 문제
코드를 다 짜고 나서야 “근데 이게 정말 원래 원했던 동작인가?”를 따져보는 경우가 있습니다. 그 시점이 늦으면 늦을수록, 이미 만들어진 코드의 모양을 바꾸기 어려워집니다. 테스트는 마지막에 “통과만 시키는 의식”으로 전락하고, 정말 검증되어야 할 경계 조건은 빠집니다.
TDD는 그 순서를 뒤집습니다. “무엇이 동작해야 하는가”를 테스트로 먼저 적고, 그것을 통과시키는 최소한의 코드를 짭니다. 결과적으로 테스트는 사후 검증이 아니라 요구사항의 첫 표현이 됩니다.
무엇을 검증하나
TDD는 범위(level)나 품질 속성(what)의 문제가 아닙니다. 언제, 어떤 순서로 쓰느냐의 문제입니다. 그래서 단위 테스트와 자주 짝지어 다뤄지지만, 통합 테스트나 E2E에도 같은 사고법을 적용할 수 있습니다.
핵심은 사이클입니다.
- Red — 실패하는 테스트를 먼저 쓴다. 아직 구현이 없으니 당연히 실패한다.
- Green — 테스트를 통과시키는 가장 단순한 코드를 짠다. 우아함은 다음 단계의 몫이다.
- Refactor — 통과하는 상태를 유지하면서 코드의 구조를 다듬는다. 테스트가 안전망 역할을 한다.
이 사이클은 짧게, 자주 도는 것이 핵심입니다. 한 사이클이 5분을 넘어가면 TDD의 리듬이 깨집니다.
설계가 바뀐다
TDD의 진짜 가치는 테스트 자체가 아니라 테스트하기 쉬운 코드를 강제한다는 점에 있습니다. 테스트가 잘 안 써진다면, 보통 코드가 너무 많은 일을 하고 있거나, 외부 의존이 깊게 박혀 있다는 신호입니다.
테스트를 먼저 쓰면 다음 같은 압력이 생깁니다.
- 함수의 입출력을 먼저 설계하게 된다 — “이 함수는 어떻게 호출되고 무엇을 반환해야 하는가”가 명확해집니다
- 의존성을 인자로 받게 된다 — 테스트에서 가짜를 주입해야 하므로, 자연스럽게 의존성 주입 구조가 됩니다
- 함수가 작아진다 — 큰 함수는 테스트가 어렵습니다. 작게 쪼개는 압력이 항상 작동합니다
- 외부 효과가 분리된다 — 순수 로직과 부수 효과(I/O, 시간, 랜덤)가 자연스럽게 떨어집니다
후행 테스트(테스트 작성 시점이 구현 이후)는 이런 압력이 약합니다. 이미 만들어진 코드를 어떻게든 테스트하려다 보니, 복잡한 mock과 setup이 늘어나고, 결국 테스트가 깨지기 쉬워집니다.
언제 잘 맞고, 언제 맞지 않나
TDD가 모든 상황에 맞는 방식은 아닙니다.
잘 맞는 경우
- 요구사항이 명확하고, 입출력이 명세 가능한 영역 (결제 계산, 권한 검증, 데이터 변환)
- 단위가 작고, 외부 의존이 적은 로직
- 장기적으로 유지보수해야 하는 핵심 비즈니스 도메인
잘 맞지 않는 경우
- 무엇을 만들지 자체가 탐색의 영역인 단계 (프로토타입, 스파이크)
- UI 시각 디자인, 애니메이션처럼 “보고 판단해야 하는” 영역
- 외부 시스템과의 통신 자체가 핵심인 코드 (이건 통합 테스트가 맞습니다)
탐색 단계에서는 TDD를 잠시 내려놓고, 형태가 잡힌 뒤에 테스트를 짜는 것도 합리적인 선택입니다. 도구가 사고를 막아서는 안 됩니다.
BDD는 무엇이 다른가
BDD(Behavior Driven Development)는 TDD의 사촌입니다. 같은 사이클을 쓰되, 테스트의 표현을 자연어 시나리오로 가져갑니다.
// TDD 스타일
it("할인율이 0.1일 때 10000원을 9000원으로 반환한다", () => {
expect(applyDiscount(10000, 0.1)).toBe(9000);
});
// BDD 스타일 (Cucumber 등)
// Given 가격이 10000원이고
// When 할인율 0.1을 적용하면
// Then 최종 가격은 9000원이다
BDD는 비개발자와의 협업이 빈번한 환경에서 강점이 있습니다. 시나리오 자체가 명세서가 되고, 그것이 그대로 자동화된 테스트가 됩니다. 다만 도구 체인이 무거워지는 비용이 있어, 모든 팀이 도입할 이유가 있는 건 아닙니다.
도입 체크리스트
TDD는 습관입니다. 한 번에 전체 코드베이스를 TDD로 바꿀 수는 없습니다.
- 새 기능을 추가할 때 한 번씩 TDD 사이클 시도
- 사이클 시간을 짧게 유지 (5분 룰)
- 테스트가 잘 안 써질 때, 설계의 신호로 해석하기
- Red 단계를 건너뛰지 않기 (실패하는 걸 직접 보는 게 중요)
- 페어 프로그래밍이나 코드 리뷰에서 함께 연습
처음에는 어색합니다. 한 달쯤 의식적으로 시도해보면 어느 영역에 잘 맞는지 감각이 생깁니다.
흔한 함정
- Red를 건너뛴다 — 테스트를 쓰고 바로 구현으로 넘어가버립니다. 그러면 그 테스트가 실제로 실패할 줄 알았는지 확인할 길이 없습니다. 한 번은 일부러 실패시켜보고 넘어가야 합니다.
- Refactor를 건너뛴다 — 통과하면 다음 테스트로 넘어가버리고, 통과한 코드는 그대로 둡니다. 사이클의 가치 절반이 사라집니다.
- 테스트가 구현을 그대로 베낀다 — 결과적으로 같은 코드를 두 번 쓰는 셈입니다. 테스트는 무엇을 원하는가를 적는 것이지, 어떻게 할 것인가가 아닙니다.
- TDD가 모든 코드의 답이라고 믿는다 — 도구는 도구입니다. 탐색 단계에서는 잠시 내려놓는 게 정답입니다.
- 사이클이 길어진다 — Green까지 30분이 걸린다면 너무 큰 단위입니다. 더 작은 테스트로 쪼개야 합니다.
참고 자료
- Kent Beck. (2002). Test Driven Development: By Example. Addison-Wesley.
- Robert C. Martin. (2008). Clean Code. Prentice Hall. (특히 9장 단위 테스트)
- Dan North. “Introducing BDD”, https://dannorth.net/introducing-bdd/
- Martin Fowler. “Test Driven Development”, https://martinfowler.com/bliki/TestDrivenDevelopment.html
다음 편 예고
함수 단위의 안전망이 자리잡았다면, 다음은 화면입니다. 컴포넌트가 의도한 모습 그대로 렌더링되는지, 디자인 시스템이 의도치 않게 바뀌지 않았는지를 어떻게 자동으로 검증할지 다룹니다.