Rails 기능을 작게 완성하는 경계 중심 TDD

테스트보다 먼저 완료 조건을 한 문장으로 쓴다

TDD를 시작할 때 가장 흔한 실수는 구현하고 싶은 클래스부터 정하는 것이다. 먼저 사용자에게 관찰되는 결과를 한 문장으로 적는 편이 낫다. 예를 들어 “비공개 글은 검색 결과에 나타나지 않는다”처럼 입력, 경계, 결과가 드러나야 한다. 이 문장은 테스트 이름이 되고, 구현 범위가 불필요하게 커지는 것도 막아 준다.

좋은 완료 조건은 참과 거짓을 기계적으로 구분할 수 있다.

  • 비공개 글의 제목을 검색해도 응답 본문에 포함되지 않는다.
  • 같은 제목으로 두 글을 만들면 slug가 서로 다르다.
  • 유효하지 않은 댓글은 저장되지 않고 오류를 표시한다.

“검색을 개선한다”나 “모델을 깔끔하게 만든다”는 검증할 수 없으므로 TDD의 출발점으로 적합하지 않다.

첫 RED는 가장 가까운 공개 경계에 둔다

모델 테스트만으로는 권한, 라우팅, 직렬화 같은 실제 실패를 놓칠 수 있다. 기능이 HTTP 요청으로 사용된다면 첫 테스트도 요청 경계에 두고, 복잡한 계산만 단위 테스트로 내려보낸다.

test "draft posts never appear in public search" do
  draft = posts(:draft)

  get search_path, params: { q: draft.title }

  assert_response :success
  assert_no_match draft.title, response.body
end

테스트를 쓴 뒤에는 반드시 실패 이유를 확인한다. 라우트 누락 때문에 404가 난다면 아직 원하는 RED가 아니다. 응답은 성공하지만 draft 제목이 노출되어 실패해야 요구사항을 정확히 겨냥한 것이다. 실패 메시지를 읽지 않고 곧바로 구현하면 엉뚱한 테스트를 통과시키기 쉽다.

GREEN에서는 통과에 필요한 최소 변경만 한다

RED를 확인했다면 현재 사례를 만족하는 가장 짧은 코드를 작성한다. 미래의 검색엔진 교체, 다중 작성자, 복잡한 정책 객체까지 미리 설계하지 않는다. 공개 조회의 핵심이 published scope라면 우선 그 경계를 한 줄로 고정한다.

def index
  @posts = Post.published.search(params[:q])
end

단, 상수를 반환하거나 fixture 이름에 맞춘 조건문처럼 테스트만 속이는 구현은 최소 구현이 아니다. 실제 입력 범위를 처리하면서도 아직 필요하지 않은 추상화를 만들지 않는 것이 GREEN의 의미다. 테스트를 통과한 직후 관련 테스트만 다시 실행해 피드백 시간을 짧게 유지한다.

REFACTOR는 동작을 바꾸지 않는 별도 단계다

초록색 상태가 된 뒤에만 중복과 이름을 정리한다. scope가 여러 컨트롤러에 반복되면 모델로 이동할 수 있지만, 한 번만 쓰이는 쿼리를 서비스 객체로 감싸는 것은 오히려 추적 비용을 높인다. 리팩터링 중에는 새로운 요구사항을 끼워 넣지 않는다.

작은 주기는 다음처럼 운영하면 안전하다.

  1. 한 가지 행동을 설명하는 테스트를 추가한다.
  2. 의도한 이유로 실패하는지 확인한다.
  3. 최소 구현으로 통과시킨다.
  4. 이름과 중복만 정리한다.
  5. 관련 테스트 후 전체 테스트를 실행한다.

리팩터링 뒤 테스트가 실패하면 변경 폭을 줄이고 어느 줄이 행동을 바꿨는지 찾는다. 실패 테스트를 완화해 초록색으로 되돌리는 것은 리팩터링이 아니다.

회귀 테스트는 실제로 깨졌던 경계를 기록한다

기능이 완성된 뒤에는 정상 사례만 남기지 말고 권한, 충돌, 악성 입력, 시간 변화 같은 경계 사례를 추가한다. slug라면 단순 중복뿐 아니라 동시에 생성될 때의 DB unique index가 필요하고, 발행 기능이라면 draft와 published 사이를 오갈 때 published_at이 어떻게 변하는지 고정해야 한다.

마지막에는 테스트 이름만 읽어도 제품 계약이 보이는지 확인한다. 테스트는 구현의 거울이 아니라 다음 개발자가 같은 실수를 반복하지 않도록 남기는 실행 가능한 문서다. 작은 RED에서 시작해 공개 경계를 검증하고 전체 스위트까지 통과시키는 습관이 TDD를 의식이 아닌 개발 도구로 만든다.

댓글

아직 댓글이 없습니다.

댓글 남기기