Skill

이제 더는 안되겠다 TDD 너 일로 나와 (with claude code)

소범범 2026. 2. 6. 17:29

 

제목 어떠셨나요?

 

더는 못참겠어서 Claude Code를 앞에 세워 TDD를 불러내 봤습니다! 😂


왜 이 글을 쓰게 되었는가?

개발자들에게 TDD는 너무 익숙한 단어이면서, 또 저같은 테스트 코드를 멀리하던 개발자에게는 두려운 단어이기도 한 것 같습니다.

 

TDD를 다들 어디서 들어보셨나요?

 

저는...

"요즘은 TDD가 대세래!"

"좋은 회사는 TDD로 개발한대!"

"TDD로 개발한 코드가 좋은 코드래!"

 

라는 말을 수도 없이 들어왔습니다.

 

그런데 솔직히 이렇게 생각했어요.

"TDD... 나도 알아! 그런데 급하게 업무가 쏟아지는데 언제 테스트 먼저 짜고 있냐고!"

 

지금 생각해보면 사실 도피였던 것 같아요. 잘 모르니까, 또 "할 상황이 안 된다"고 핑계를 대고 있었던 거죠.

 

이번에 루퍼스에서 회원 API(회원가입, 내 정보 조회, 비밀번호 변경)를 구현하면서 Claude Code와 함께 TDD를 정면으로 부딪혀봤습니다. 그 과정에서 겪은 고민들을 나눠볼게요.


TDD란? (짧게!)

 

TDD(Test-Driven Development)는 “테스트를 먼저 쓰고, 그 테스트를 통과시키는 최소한의 구현을 한 뒤, 리팩토링으로 코드를 정리하는” 개발 방식. 핵심은 기능을 만들기 전에 “이 기능이 맞게 동작한다”는 기준을 테스트로 먼저 고정해두는 것.

 

TDD = 테스트 먼저 → 구현 → 리팩토링 (🔴Red → 🟢Green → 🔵Refactor)

 

개념 설명은 여기까지! 더 자세한 건 구글링 😄


 

고민 1: TDD Red 단계, 컴파일 에러도 "실패"인가?

 

TDD의 첫 단계는 Red - 실패하는 테스트를 먼저 작성하는 거잖아요.

그런데 막상 테스트를 먼저 작성하니까...

 

@Test
fun createsMember_whenValidInfoProvided() {
    val command = SignUpCommand(...)  // ❌ 클래스가 없음
    val result = memberService.signUp(command)  // ❌ 메서드가 없음
    assertThat(result.loginId).isEqualTo("testuser1")
}

 

컴파일 에러가 발생했습니다.

"이게 진짜 '실패하는 테스트'인가? 테스트가 실행조차 안 되는데?"

 

내가 선택한 방식

 

고민 끝에 내린 결론: 컴파일 에러도 실패다.

 

테스트 코드가 설계 문서 역할을 한다고 생각하니 납득이 됐어요.

 

"이 테스트를 통과시키려면 뭘 만들어야 하지?"라는 질문에 답하는 과정이 곧 설계니까요.

 

실제로 이렇게 진행했습니다:

1. 테스트 먼저 작성 (컴파일 에러 - Red)
2. 최소 구현체 생성 → throw NotImplementedError() (기능적 실패 확인)
3. 실제 로직 구현 (Green)
4. 리팩토링 (Refactor)

 

고민 2: Mock vs Stub, 뭘 써야 하지?

 

Stub = 정해진 값 반환 / Mock = 호출 여부 검증 (verify())

 

내 선택: Stub만 사용

 

// Stub - 값 반환 (내가 사용한 방식)
whenever(memberRepository.save(any())).thenAnswer { it.arguments[0] }

// Mock - 호출 검증 (사용 안 함)
verify(memberRepository).save(any())

 

왜 verify() 안 썼냐고요?

 

signUp()Member반환하거든요. 반환값으로 검증하면 되는데 굳이 "호출됐냐?"까지 확인할 필요가 없었어요.

 

val result = memberService.signUp(command)
assertThat(result.loginId).isEqualTo("testuser1")  // 이걸로 충분!

 

verify()는 void 메서드나 이메일 발송 같은 부수 효과를 검증할 때 쓰면 됩니다.

 


 

고민 3: 단위 vs 통합 vs E2E

 

단위 = Mock / 통합 = 실제 DB / E2E = HTTP 전체 흐름

 

내 실수: 통합 테스트를 깜빡했다

 

처음에 단위 + E2E만 작성하고 "끝!" 했는데... Claude가 물어봤어요.

 

"통합 테스트는요?"

 

아차 싶었습니다. 단위 테스트는 Mock이라 진짜 DB에 저장되는지는 모르거든요.

// 단위: "save 호출하면 이거 반환해~" (실제 저장 X)
whenever(memberRepository.save(any())).thenAnswer { it.arguments[0] }

// 통합: 진짜 저장됐는지 DB에서 꺼내서 확인
val savedMember = memberJpaRepository.findByLoginId("testuser1")!!

 

결국 단위 → 통합 → E2E 다 작성했습니다. 빠뜨릴 뻔했네요!

 

그래도 이런 실수 덕분에 TDD의 테스트 구조에 대해서는 명확하게 알아가는 단계가 되었어요.😂


 

고민 4: TDD 커밋은 언제?

 

내 선택: 기능 완성 후 한 번에

🔴 테스트 작성 → 🟢 구현 → 🔵 리팩토링 → ✅ 커밋!

 

Red 단계마다 커밋하면 누가 커밋 잘못 받음녀 CI/CD가 터질수 있지 않나?

 

혹시, 실무에서 사용하시는 분들은 어떤 기준으로 커밋하시나요?? 를 claude에게 물어봤습니다.

 

claude 의 답변은? 

 

TDD는 "개발 방법론"이지, 커밋 전략이 아닙니다. 작업은 TDD로, 커밋은 동작하는 단위로!


 

고민 5: @Nested로 테스트 정리

 

테스트가 많아지니까 @Nested로 그룹핑했어요. 테스트 리포트가 깔끔해지더라고요.

@Nested inner class SignUp { ... }
@Nested inner class GetMyInfo { ... }

 

필수는 아닌데, 테스트 많아지면 꽤 유용할것 같습니다!


 

🔥 그래서 결국 Claude + TDD, 뭐가 좋았나?

1. Red 단계에서 테스트 케이스 폭발

 

솔직히 제가 혼자 테스트 케이스 짜면 이 정도였을 거예요:

- 회원가입 성공
- 중복 아이디 실패
- 끝?

 

근데 Claude한테 "회원가입 테스트 케이스 짜줘"라고 하니까:

- 회원가입 성공
- 중복 아이디 실패
- 비밀번호 8자 미만
- 비밀번호 16자 초과
- 비밀번호에 생년월일 포함
- 비밀번호 특수문자 없음
- 이메일 형식 오류
- ...

 

경계값, 예외 케이스를 촘촘하게 뽑아줘요. 제가 놓치는 부분을 Claude가 잡아줬습니다.

 

제가 AI를 사용할 때 고민하는 부분은 "명확한 가이드라인을 어떻게 줘야 하나?"인 것 같아요.

 

프롬프팅 엔지니어라는 직군이 생겨날 정도로 AI에게 질문, 즉 가이드라인을 정해주는 건 아주 중요한 일이잖아요?

 

그래서 저는 TDD와 AI는 최고의 조합인 것 같아요.

 

"TEST 코드를 먼저 작성하는 것 = AI에게 가이드라인을 주고 개발을 시작하는 것"

 

 

이라고 생각했거든요!

 

이제 반대로 좋은 테스트 코드를 어떻게 작성할 수 있을까요?

 

더 정확하고 엄격한 테스트 코드를 통과하게 하는 게 좋지 않을까요?

 

AI는 우리의 요구사항을 듣고 휴먼보다 더 많은 테스트 케이스를 생각해낼 수 있어요!

 

AI로 더 명확하고 다양한 테스트 코드를 만들고, 그 테스트 코드를 기반으로 AI가 개발을 하면 너무 좋지 않을까요?

 

네, 그 답이 TDD + AI => TDD + 바이브 코딩이라고 생각했습니다!

 

2. Green 단계에서 구현 속도

 

테스트가 명확하니까 Claude가 구현을 정말 빠르게 해요.

 

"이 테스트 통과시켜줘" → 바로 구현 완성

 

테스트가 스펙 문서 역할을 하니까 Claude 입장에서도 "뭘 만들어야 하는지" 명확한 거죠.

 

3. Refactor 단계에서 안전망

 

TDD의 숨은 장점은 리팩토링이 두렵지 않다는 거예요.

 

Claude한테 "이 코드 리팩토링해줘"라고 해도, 테스트가 있으니까:

  • 잘못 고치면 바로 테스트 실패
  • "이거 바꿔도 되나?" 걱정 없음
  • 과감하게 구조 변경 가능
리팩토링 전: 테스트 통과 ✅
리팩토링 후: 테스트 실패 ❌ → 뭔가 잘못됐네!
리팩토링 후: 테스트 통과 ✅ → 안심하고 커밋!

 

4. "테스트가 문서다"

 

테스트 코드 자체가 요구사항 명세서가 됐어요.

 

@DisplayName("비밀번호가 8자 미만이면, BAD_REQUEST 예외가 발생한다.")
@DisplayName("비밀번호에 생년월일이 포함되면, BAD_REQUEST 예외가 발생한다.")

 

이 테스트들을 보면 "아, 비밀번호 규칙이 이렇구나" 바로 알 수 있잖아요.

 

나중에 다른 개발자가 와도, 테스트만 보면 이 서비스가 뭘 하는지 알 수 있습니다.

 


 

⚠️ Claude + TDD, 주의할 점!

 

1. Claude가 생성한 테스트를 무조건 믿지 마세요

 

Claude가 테스트 케이스를 20개 만들어줬다고 다 필요한 건 아니에요.

 

실제로 이런 일이 있었습니다:

 

Claude: jdbc url 스키마를 yoonbeom_test로 설정했어요.

나: "어...? 그런 스키마는 없는데 내 DB는 현재 yoonbeom 스키마만 있어"

 

현재 스코프에서 필요한 테스트인지 판단은 휴먼이 해야 합니다.

 

2. "왜 이 테스트를 짜는지" 이해해야 해요

 

Claude가 짜준 테스트를 그냥 복붙하면, 나중에 테스트 실패했을 때 왜 실패했는지 모릅니다.

// 이 테스트가 뭘 검증하는 건지 모르고 넣으면...
assertThat(passwordEncoder.matches(rawPassword, savedMember.password)).isTrue()

 

"passwordEncoder가 뭐지?", "왜 matches로 비교하지?" 이해 없이 넣으면 나중에 고생해요.

 

테스트 하나하나 이해하면서 가세요.

 

그렇다고 삽질하라는 말은 아닙니다!

 

이해 안 되면 Claude한테 물어보면 됩니다!

 

3. TDD 사이클을 Claude한테 맡기지 마세요

 

이건 제가 실수한 부분인데...

 

처음에 "회원가입 TDD로 개발해줘"라고 통째로 맡겼어요.

 

결과: Claude가 테스트 + 구현 + 리팩토링을 한 번에 해버림 😅

 

그건 TDD가 아니라 그냥 "테스트랑 코드 같이 짜기"예요.

 

(제가 생각하는) 올바른 방법:

 

나: "회원가입 테스트 케이스만 짜줘" (Red)
Claude: 테스트 코드 생성
나: (테스트 확인 후) "이제 구현해줘" (Green)
Claude: 구현 코드 생성
나: (동작 확인 후) "리팩토링할 부분 있어?" (Refactor)

 

사이클 컨트롤은 휴먼이! Claude는 각 단계의 실행자로 활용하세요.

 

4. 테스트 커버리지에 집착하지 마세요

 

Claude한테 "테스트 더 짜줘"라고 하면 계속 짜줍니다. 무한히요.

 

"이 메서드 null 처리 테스트"
"이 메서드 빈 문자열 테스트"
"이 메서드 공백만 있는 테스트"
"이 메서드..."

 

100% 커버리지가 목표가 아닙니다. 의미 있는 테스트가 목표예요.

 

"이 테스트가 진짜 필요한가?"는 휴먼이 판단해야 합니다.

 

5. Claude가 못 잡는 것들

 

Claude는 비즈니스 로직의 정합성을 완벽히 판단하지 못해요.

 

예를 들어:

  • "우리 서비스에서 비밀번호 규칙이 이게 맞나?"
  • "이 예외 메시지가 사용자한테 적절한가?"
  • "이 API 응답 구조가 프론트엔드랑 맞나?"

 

이런 건 도메인을 아는 휴먼만 판단할 수 있습니다.

 


 

결론: 야 TDD 너 앞으로 Claude 랑 함께 가자

 

좋았던 점:

  • 테스트 케이스 생성 → Claude가 경계값/예외 촘촘하게 잡아줌
  • 구현 속도 → 테스트가 스펙이 되니까 Claude가 빠르게 구현
  • 리팩토링 → 테스트가 안전망이 되어 과감하게 수정 가능

 

주의할 점:

  • 생성된 테스트 무조건 믿지 말고 선별하기
  • 테스트 이해 없이 복붙하지 않기
  • TDD 사이클 컨트롤은 휴먼이 하기
  • 커버리지 집착 금지

 


 

TDD, 더 이상 도망치지 않고 정면으로 부딪혀봤습니다.

 

솔직히 Claude 없이 혼자 했으면 중간에 포기했을 것 같아요. 테스트 케이스 하나하나 생각하고, 구현하고, 리팩토링하고... 힘들었을 것 같아요😂

 

근데 Claude랑 같이 하니까 Red 단계의 리소스가 확 줄었어요. 그러니까 Green-Refactor도 자연스럽게 따라왔고요.

 

TDD가 어렵다면, AI와 함께 시작해보세요. 진입 장벽이 확 낮아집니다.

 

다음엔 더 복잡한 도메인에서 TDD를 적용해보고 싶습니다! 🚀

 

긴 긁 읽어 주셔서 감사합니다!!