기능을 구현한 뒤 테스트를 붙이면 이미 만든 코드에 맞춰 테스트를 작성하기 쉽다. 테스트는 모두 통과하지만 사용자가 기대한 시나리오가 빠진 상태도 가능하다.

그래서 나는 기능 전체의 동작을 인수 테스트로 먼저 명세하고, 그 기능을 이루는 세부 규칙은 주로 단위 테스트의 TDD 주기로 채워 나간다. 이 글에서는 이 방식을 ATDD × TDD라고 부르겠다.

인수 테스트로 기능의 완료 조건을 먼저 정하고, 단위 테스트의 짧은 TDD 피드백을 반복하며 그 조건을 만족시킨다.

먼저 기능 전체의 완료 조건을 정한다

ATDD는 서로 다른 관점의 참여자가 구현 전에 인수 조건을 합의하고, 그 조건으로 기능을 검증하는 개발 방식이다. TDD도 단위 테스트라는 특정 수준이 아니라 테스트를 먼저 작성하며 구현을 이끄는 개발 주기다. 내 흐름에서는 ATDD로 정한 핵심 시나리오를 API 통합 테스트로 자동화하고, 세부 구현에서는 주로 단위 테스트로 TDD 주기를 돌린다.

Spring 서버의 인수 테스트에서는 HTTP 응답과 함께 회원 저장처럼 요구사항에 포함된 최종 상태를 확인한다. 이를 통해 기능 전체가 요구사항대로 연결됐는지 본다.

예를 들어 회원 가입을 구현하기 전에 다음 시나리오를 쓴다.

Feature: 회원 가입

Scenario: 정상적으로 회원 가입한다
  Given 가입되지 않은 이메일이다
  When 회원 가입을 요청한다
  Then 201 Created를 반환한다
  And 회원이 ACTIVE 상태로 저장된다

Scenario: 이미 가입된 이메일로 회원 가입한다
  Given 이미 가입된 이메일이다
  When 회원 가입을 요청한다
  Then 409 Conflict를 반환한다

Scenario: 비밀번호 정책을 만족하지 않는다
  Given 가입되지 않은 이메일이다
  When 정책을 만족하지 않는 비밀번호로 회원 가입을 요청한다
  Then 400 Bad Request를 반환한다

이 시나리오는 기능이 언제 완료됐다고 판단할지 정한다. 코드를 쓰기 전에 정상 응답, 중복 가입, 잘못된 입력을 어디까지 처리해야 하는지 드러난다. 구현자가 빈칸을 임의로 채우기 전에 요구사항을 확인할 수도 있다.

세부 구현은 단위 테스트의 TDD 주기로 채운다

인수 테스트를 작성하고 실행하면 아직 기능이 없으므로 실패한다. 이제 이 테스트를 통과시키는 데 필요한 규칙을 작은 단위로 나누고 TDD의 Red → Green → Refactor를 반복한다.

회원 가입의 비밀번호 정책이라면 세부 조건은 단위 테스트에서 확인한다.

PasswordPolicyTest
- 8자 미만이면 실패한다
- 숫자가 없으면 실패한다
- 특수문자가 없으면 실패한다
- 모든 조건을 만족하면 통과한다

회원 생성 규칙이나 이메일 형식처럼 다른 세부 로직도 같은 방식으로 구현한다. 작은 테스트가 하나씩 통과할수록 인수 테스트가 요구한 전체 동작에 가까워진다.

인수 조건 합의

인수 테스트 Red

단위 테스트 Red → Green → Refactor
      ↓                 ↑
      └──── 다음 규칙 ────┘

인수 테스트 Green

기능이 완성되면 처음 작성한 인수 테스트가 통과하는지 확인한다. 이후 리팩터링할 때도 단위 테스트와 인수 테스트를 함께 실행한다.

이 조합이 좋은 이유

완료 기준이 구현보다 먼저 생긴다

인수 테스트는 “코드를 얼마나 작성했는가”가 아니라 “사용자 관점에서 기능이 동작하는가”를 완료 기준으로 만든다. 구현 중 방향이 흔들려도 처음 합의한 시나리오로 돌아갈 수 있다.

두 크기의 피드백을 함께 얻는다

단위 테스트는 세부 규칙의 실패를 빠르고 정확하게 알려준다. 인수 테스트는 각각의 규칙과 계층을 연결했을 때 기능 전체가 제대로 동작하는지 알려준다. 하나만 사용할 때보다 실패 범위와 원인을 모두 파악하기 쉽다.

회귀와 리팩터링을 서로 다른 수준에서 막는다

단위 테스트는 비밀번호 정책이나 상태 변화 같은 내부 규칙을 보호한다. 인수 테스트는 API 응답처럼 사용자에게 보이는 동작과 요구사항에 포함된 최종 상태를 보호한다. 내부 구조를 바꿔도 두 테스트가 모두 통과하면 세부 규칙과 전체 기능을 함께 확인한 셈이다.

AI가 구현할 때도 목적지가 분명하다

AI 에이전트가 구현과 테스트를 함께 만들면 자신이 이해한 설계를 스스로 증명할 수 있다. 사람이 먼저 확인한 인수 조건을 주면 에이전트가 임의로 기능 범위를 정하는 일을 줄일 수 있다. 에이전트는 정해진 목적지 안에서 단위 TDD를 반복하고, 마지막에 인수 테스트로 전체 결과를 확인한다.

인수 테스트는 얇고 굵게 둔다

모든 예외 조합을 인수 테스트로 만들면 테스트가 느려지고 준비할 데이터도 많아진다. 회원 가입이라면 정상 가입, 중복 이메일, 정책 위반처럼 핵심 시나리오만 인수 테스트로 둔다. 비밀번호 길이, 숫자 포함 여부, 특수문자 포함 여부 같은 조합은 단위 테스트에서 검증한다.

인수 테스트만으로는 세부 실패를 빠르게 찾기 어렵고, 단위 테스트만으로는 기능 전체가 요구사항대로 연결됐는지 알기 어렵다. 기능 전체는 인수 테스트로 먼저 명세하고, 세부 구현은 단위 TDD로 채우는 것이 내가 선택해 사용하고 있는 개발 방식이다.