TL;DR: 테스트 코드 프롬프트를 작성하기 위해서는 설계에 기반한 요구사항을 명확하게 정의해야 한다.
테스트 코드는 항상 뒤로 밀린다. 기능은 돌아가고, 일정은 촉박하고, 테스트는 나중에가 된다.
Claude Code를 쓰기 시작했을 때, 처음엔 테스트 코드를 대신 써주는 도구로 기대했다. 그런데 몇 번 실패하고 나서 방향을 바꿨다. AI에
게 코드를 맡기기 전에, 내가 먼저 생각을 정리하기로 했다.
0. 프롬프트를 잘 쓰는 게 아니라, 쪼개는 게 중요하다
한 번에 다 물어보면 결과는 항상 비슷했다. 무엇보다 AI의 판단이 내 생각과 다르기도 했고, 구체적인 방향 제시가 어려웠다. 그래서 전략을 바꿨다.
요구사항 재정의 > 테스트 코드 설계 및 검증 > 구현 요청
위와 같이 프롬프트를 여러 단계로 쪼갰다. 바로 구현을 요구하지 않고, 내게 판단을 맡기도록 했다. 이때부터 Claude Code는 코드 생성기가 아니라 설계 보조 도구가 됐다.
1. 요구사항 재정의
AI를 위해 요구사항을 명료하게 재정의하면서, 테스트 시나리오의 대부분은 코드를 쓰기 전에 결정된다고 느꼈다. 빼먹지 않으려고 노력한 부분은 아래와 같다.
- 기능 목록 정리
- 반드시 지켜야 할 제약 조건
- 애매하거나 추가로 결정이 필요한 포인트
아래는 회원가입 API 명세이다.
| 항목 | 내용 |
| 입력 | loginId, password, name, birthDate, email |
| 출력 | 201 + userId |
| 제약 | 중복 ID 불가, 각 필드 검증, 비밀번호 BCrypt 암호화 |
아래는 AI Agent가 내게 애매한 포인트를 질문해, 내가 판단해서 확정한 User 도메인 검증 규칙 명세이다.
| 필드 | 규칙 |
| loginId | 영문+숫자만, 30바이트 이하 |
| name | 한글+영문만, 30바이트 이하 |
| birthDate | YYYYMMDD 포맷, 유효한 날짜 |
| 표준 이메일 형식 | |
| password | 8~16자, 영문 대소문자/숫자/특수문자(!@#$%^&*)만, 생년월일 4자리 이상 부분문자열 포함 불가 |
2. 테스트 코드 설계 및 검증
도메인과 책임을 나누면서 계속 던진 질문은 이것이었다. 이 책임은 엔티티가 가져야 할까, 서비스가 가져야 할까? 비밀번호 규칙 검증, 로그인 ID 중복 확인, 암호화 처리. 모두 “될 수는 있지만, 그래도 되는가?”를 기준으로 나눴다. 특히 비밀번호 검증은 불필요한 정보 노출을 막기 위해 검증 순서까지 명확히 정의했다.
- 애매한 검증 책임은 내게 묻게 해 내 판단에 따르도록 했다.
- 성공 케이스가 아니라 실패 케이스 위주로 테스트 코드를 요청했다.
- 검증 순서에 따라 에러 코드를 정하니 책임과 테스트의 범위가 자연스럽게 정리됐다.
- 비밀번호 규칙 위반 및 예외 처리 방식을 제안하게 하고 내가 선택할 수 있었다.
- 내 정보 조회 API에서는 이름 마스킹이 필요했다. 하지만 이 로직을 엔티티에 두지는 않았다. 엔티티는 원본 데이터를 보존해야 한다고 생각했기 때문이다. 마스킹은 표현의 문제라 보고 DTO에서 처리했다.
3. 구현 요청
요구사항과 테스트 설계가 끝난 뒤에야 구현을 요청했다. 이 단계에서 AI에게 기대한 건 똑똑한 판단이 아니라, 이미 합의된 설계를 정확히 코드로 옮기는 능력이었다. 구현 프롬프트에는 새로운 판단을 요구하지 않았다. 대신 앞 단계에서 확정된 내용을 그대로 전달했다.
- 도메인 객체가 책임질 검증 범위
- 서비스 레이어에서 처리해야 할 검증과 예외
- 검증 실패 시의 에러 코드와 발생 순서
- 테스트 코드에서 이미 가정하고 있는 인터페이스와 동작
특히 테스트 코드를 먼저 설계해 두었기 때문에, 구현 요청은 자연스럽게 “이 테스트를 통과하는 코드를 작성하라”는 형태가 됐다. 구현은 목적이 아니라 결과물이었고, 테스트가 사실상 스펙 역할을 했다.
이 방식의 장점은 구현 단계에서의 대화가 거의 필요 없다는 점이었다. AI가 임의로 책임을 옮기거나, 검증 순서를 바꾸거나, 예외 타입을 새로 만들 여지가 없었기 때문이다. 만약 코드가 마음에 들지 않는다면, 구현을 고칠 필요 없이 테스트나 설계로 다시 돌아가면 됐다.
또 하나 인상적이었던 점은, 테스트 코드가 이미 실패 케이스 중심으로 잘게 쪼개져 있으니 구현 코드도 자연스럽게 단순해졌다는 것이다.
“이 분기에서 무엇을 해야 하지?”가 아니라, “이 테스트를 만족하려면 여기서 무엇을 반환해야 하지?”라는 식으로 사고가 정리돼 있었다.
결과적으로 구현된 코드는 몇 가지 사소한 네이밍 수정 외에는 거의 손댈 필요가 없었다. 이전 같았으면 구현 → 테스트 → 수정 → 테스트의 반복이었겠지만, 이번에는 설계 → 테스트 → 구현이라는 단방향 흐름이 만들어졌다.
정리하며
Claude Code로 테스트 코드를 쓰면서 느낀 건, AI는 사고를 대신해주지 않는다는 점이다. 잘 정리된 사고를 빠르게 코드로 옮겨줄 뿐이다. 이는 단순하고 재미없는 작업으로 시간이 많이 걸리는 테스트 코드에 최고의 특성이라고 생각한다. 문법보다 먼저 생각의 순서를 점검해 볼 시간을 충분히 얻을 수 있었고, 꼼꼼하게 설계한 프롬프트를 AI Agent에게 맡기면서 앞으로는 모든 기능에 테스트 코드를 짜는 게 정말 어렵지 않을 거라고 생각한다.
'Programming > Technical Writing' 카테고리의 다른 글
| 외부 결제 시스템(PG) 연동에서 Circuit Breaker가 필요한 이유 (0) | 2026.03.20 |
|---|---|
| 조회 병목 해소 과정: 쿼리, 인덱스, 비정규화, Redis (0) | 2026.03.13 |
| 동시성 제어: 트랜잭션 락은 언제 쓰는가 (0) | 2026.03.06 |
| 협업/분리/확장 관점에서 DIP 쓰는 법 (0) | 2026.02.27 |
| 주니어는 모르는 ERD 설계의 함정들 (1) | 2026.02.13 |