경계, 불변성, 실패 모드를 겨냥해 해피 패스 대신 고신호 테스트를 생성하는 Claude Code 테스트 생성 프롬프트를 배워보세요.

자동 생성된 테스트 스위트는 겉으로 보기엔 인상적입니다: 수십 개의 테스트, 많은 셋업 코드, 거의 모든 함수명이 어딘가에 등장합니다. 하지만 그 테스트들 중 상당수는 단순히 “정상일 때 동작한다”는 확인에 불과합니다. 쉽게 통과하고, 버그를 거의 잡지 못하며, 읽고 유지하는 데 시간만 듭니다.
일반적인 Claude Code 테스트 생성 프롬프트에서는 모델이 본 예시 입력을 따라가는 경향이 있습니다. 겉으로 달라 보이는 변형들이 나오지만 실제로는 같은 동작만을 다루죠. 결과는 핵심을 제대로 커버하지 못하는 방대한 테스트 모음입니다.
고신호 테스트는 다릅니다. 최근의 사고를 잡아냈을 소수의 테스트들입니다. 위험한 방식으로 동작이 바뀌면 실패하고, 무해한 리팩터링에는 안정적입니다. 하나의 고신호 테스트는 스무 개의 “기대값을 반환한다” 확인보다 더 가치 있을 수 있습니다.
저가치 해피-패스 생성에는 몇 가지 뚜렷한 징후가 있습니다:
예를 들어 할인 코드를 적용하는 함수가 있다고 합시다. 해피-패스 테스트는 “SAVE10”이 가격을 낮춘다를 확인합니다. 실제 버그는 다른 곳에 숨어 있습니다: 0 이거나 음수인 가격, 만료된 코드, 반올림 경계, 최대 할인 한도 등. 그런 케이스들이 잘못된 합계, 화난 고객, 자정의 롤백을 유발합니다.
목표는 “테스트를 더 많이” 만드는 것에서 “더 좋은 테스트”로 옮기는 것입니다. 이를 위해 세 가지 목표를 노리세요: 경계(boundaries), 실패 모드(failure modes), 불변성(invariants).
고신호 단위 테스트를 원한다면 “더 많은 테스트”를 요구하는 것을 멈추고 세 가지 특정 유형을 요청하세요. 이것이 Claude Code 테스트 생성 프롬프트의 핵심입니다. 쓸모없는 "정상 입력에서 동작" 확인 더미 대신 실용적인 커버리지를 만듭니다.
경계는 코드가 허용하거나 생성하는 것의 가장자리입니다. 많은 실제 결함은 오프바이원, 빈 상태, 타임아웃 문제 등 해피 패스에서는 나타나지 않습니다.
최소/최대(0, 1, 최대 길이), 빈 vs 존재("", [], nil), 오프바이원(n-1, n, n+1), 시간 제한(컷오프 근처) 같은 관점으로 생각하세요.
예: API가 “최대 100개 항목”을 허용한다면 3개만 테스트하지 말고 100과 101을 테스트하세요.
실패 모드는 시스템이 고장나는 방식입니다: 잘못된 입력, 누락된 의존성, 부분 결과, 상류 오류 등. 좋은 실패 모드 테스트는 이상적인 조건에서의 출력만 확인하지 않고 스트레스하에서의 동작을 검증합니다.
예: 데이터베이스 호출이 실패하면 함수가 명확한 오류를 반환하고 부분적인 데이터 기록을 피하는가?
불변성은 호출 전후에 항상 참이어야 하는 진리들입니다. 이것들은 모호한 올바름을 날카로운 어서션으로 바꿉니다.
예:
이 세 가지를 집중하면 테스트 수는 줄어들지만 각 테스트의 시그널은 커집니다.
너무 일찍 테스트를 요청하면 공손한 “예상대로 동작한다” 확인 묶음이 나오기 쉽습니다. 간단한 해결책은 먼저 작은 계약을 작성한 다음 그 계약에서 테스트를 생성하는 것입니다. Claude Code 테스트 생성 프롬프트를 실제 버그를 찾는 도구로 바꾸는 가장 빠른 방법입니다.
유용한 계약은 한숨에 읽을 수 있을 만큼 짧아야 합니다. 입력, 출력, 그리고 다른 어떤 변경이 일어나는지를 답하는 5~10줄을 목표로 하세요.
계약은 코드가 아니라 평이한 언어로 작성하고, 테스트할 수 있는 내용만 포함하세요.
계약을 작성한 후에는 현실이 가정들을 깨뜨릴 수 있는 지점을 스캔하세요. 그 지점들이 경계(최소/최대, 0, 오버플로우, 빈 문자열, 중복)와 실패 모드(타임아웃, 권한 거부, 고유 제약 위반, 손상된 입력)가 됩니다.
작업 예: reserveInventory(itemId, qty) 같은 기능을 위한 계약
계약에는 qty가 양의 정수여야 하고 함수는 원자적이어야 하며 음수 재고를 만들면 안 된다고 쓴다면 즉시 고신호 테스트가 떠오릅니다: qty = 0, qty = 1, 사용 가능한 수량보다 큼, 동시 호출, 데이터베이스 오류를 강제로 발생시켰을 때 등.
Koder.ai 같은 비브-코딩(vibe-coding) 도구를 사용한다면 같은 워크플로를 적용하세요: 먼저 채팅에서 계약을 작성한 다음 그 계약을 직접 공격하는 테스트를 생성하세요.
보다 적지만 각 테스트가 의미 있는 결과를 내는 테스트를 원할 때 이 Claude Code 테스트 생성 프롬프트를 사용하세요. 핵심은 먼저 테스트 계획을 강제하고, 계획을 승인한 후에 테스트 코드를 생성하게 하는 것입니다.
You are helping me write HIGH-SIGNAL unit tests.
Context
- Language/framework: \u003cfill in\u003e
- Function/module under test: \u003cname + short description\u003e
- Inputs: \u003ctypes, ranges, constraints\u003e
- Outputs: \u003ctypes + meaning\u003e
- Side effects/external calls: \u003cdb, network, clock, randomness\u003e
Contract (keep it small)
1) Preconditions: \u003cwhat must be true\u003e
2) Postconditions: \u003cwhat must be true after\u003e
3) Error behavior: \u003chow failures are surfaced\u003e
Task
PHASE 1 (plan only, no code):
A) Propose 6-10 tests max. Do not include “happy path” unless it protects an invariant.
B) For each test, state: intent, setup, input, expected result, and WHY it is high-signal.
C) Invariants: list 3-5 invariants and how each will be asserted.
D) Boundary matrix: propose a small matrix of boundary values (min/max/empty/null/off-by-one/too-long/invalid enum).
E) Failure modes: list negative tests that prove safe behavior (no crash, no partial write, clear error).
Stop after PHASE 1 and ask for approval.
PHASE 2 (after approval):
Generate the actual test code with clear names and minimal mocks.
실용적인 트릭은 경계 매트릭스를 간결한 표로 요구하는 것입니다. 그러면 빈칸이 쉽게 드러납니다:
| Dimension | Valid edge | Just outside | “Weird” value | Expected behavior |
|---|---|---|---|---|
| length | 0 | -1 | 10,000 | error vs clamp vs accept |
만약 Claude가 20개의 테스트를 제안하면 반박하세요. 유사한 케이스를 합치고 실제 버그(오프바이원, 잘못된 오류 타입, 무언의 데이터 손실, 불변성 파괴)를 잡을 것만 남기라고 요구하세요.
원하는 동작에 대한 작고 구체적인 계약으로 시작하세요. 함수 시그니처, 입력/출력의 간단한 설명, 기존 테스트(해피-패스뿐이라도)를 붙여넣으면 모델이 실제 코드에 기반해 동작하게 됩니다.
다음으로 테스트 코드를 요청하기 전에 리스크 테이블을 요구하세요. 세 열을 요구합니다: 경계 케이스(유효 입력의 가장자리), 실패 모드(잘못된 입력, 누락 데이터, 타임아웃), 불변성(항상 유지되어야 하는 것). 각 행에 한 문장으로 “왜 이것이 깨질 수 있는가”를 적으세요. 간단한 표가 수많은 테스트 파일보다 빠르게 누락된 부분을 드러냅니다.
그다음 각 테스트가 고유한 버그 포착 목적을 가지도록 가장 작은 테스트 집합을 선택하세요. 두 테스트가 같은 이유로 실패하면 더 강한 쪽을 남기세요.
실용적인 선택 규칙:
마지막으로 각 테스트에 한 줄 설명을 추가하세요: 실패 시 어떤 버그를 잡았을지. 설명이 모호하면(“동작을 검증한다” 등) 그 테스트는 아마 저가치입니다.
불변성은 어떤 유효한 입력을 주더라도 항상 참이어야 하는 규칙입니다. 불변성 기반 테스트에서는 먼저 규칙을 평이한 문장으로 쓰고, 이를 실패 시 명확히 드러나는 어서션으로 바꿉니다.
1~2개의 실제로 보호해 주는 불변성을 선택하세요. 좋은 불변성은 안전성(데이터 손실 없음), 일관성(같은 입력에 같은 출력), 한도(상한 초과 금지) 등입니다.
불변성을 짧은 문장으로 작성한 다음 테스트가 관찰할 수 있는 증거를 결정하세요: 반환값, 저장된 데이터, 방출된 이벤트, 의존성 호출 등. 강한 어서션은 결과와 부작용을 모두 확인합니다. 많은 버그가 “응답은 OK지만 잘못 쓴 경우”에 숨어 있습니다.
예: 쿠폰을 주문에 적용하는 함수가 있다면
이를 다음과 같이 어서션으로 옮깁니다:
expect(result.total).toBeGreaterThanOrEqual(0)
expect(db.getOrder(orderId).discountCents).toBe(originalDiscountCents)
“기대되는 결과를 반환한다” 같은 모호한 어서션을 피하고 구체적 규칙(비음수), 구체적 부작용(할인이 한 번만 저장됨)을 확인하세요.
각 불변성마다 어떤 데이터가 그것을 위반할지에 대한 짧은 메모를 테스트에 추가하세요. 이렇게 하면 테스트가 시간이 지나며 해피-패스 확인으로 흐려지는 것을 막을 수 있습니다.
간단한 패턴:
고신호 테스트는 종종 코드가 안전하게 실패한다는 것을 확인하는 테스트입니다. 모델이 해피-패스 테스트만 작성하면 입력과 의존성이 엉망일 때 기능이 어떻게 동작하는지 거의 알 수 없습니다.
먼저 이 기능에 대해 “안전하게 실패한다”가 무엇을 의미하는지 정하세요. 명확한 타입의 오류를 반환하는가? 기본값으로 폴백하는가? 한 번 재시도한 후 중단하는가? 한 문장으로 기대 동작을 적고 테스트로 증명하세요.
Claude Code에 실패 모드 테스트를 요청할 때 목표를 엄격히 하세요: 시스템이 깨질 수 있는 방식들을 다루고 원하는 정확한 응답을 어서션하세요. 유용한 한 줄은: “많은 얕은 테스트보다 더 강한 어서션을 가진 적은 수의 테스트를 선호한다.”입니다.
좋은 테스트를 만들어주는 실패 카테고리:
예: 사용자 생성 엔드포인트가 회원 가입 후 환영 이메일을 보내는 서비스에 호출한다고 합시다. 저가치 테스트는 “201을 반환한다”만 확인합니다. 고신호 실패 테스트는 이메일 서비스가 타임아웃될 때 사용자를 생성하고 201과 함께 "email_pending" 플래그를 반환하는지, 아니면 503을 반환하고 사용자를 생성하지 않는지 중 어떤 동작을 택하는지 확인합니다. 한 동작을 선택하고 응답과 부작용을 모두 어서트하세요.
또한 노출하지 말아야 할 것을 테스트하세요. 유효성 검사 실패 시 DB에 아무 것도 쓰이지 않는지, 의존성이 손상된 페이로드를 반환하면 처리되지 않은 예외나 스택 트레이스가 반환되지 않는지를 확인하세요.
저가치 테스트 세트는 보통 모델이 양적 보상을 받을 때 생깁니다. Claude Code 테스트 생성 프롬프트가 "단위 테스트 20개"를 요청하면 거의 같은 변형이 많이 나오지만 새로운 것을 잡지 못합니다.
흔한 함정:
예: "사용자 생성" 기능이 있다고 가정합시다. 열 개의 해피-패스 테스트는 이메일 문자열만 바꿀 수 있고 중요한 것을 놓칠 수 있습니다: 중복 이메일 거부, 빈 비밀번호 처리, 반환된 사용자 ID의 고유성 보장 등.
검토를 돕는 가드레일:
기능: 체크아웃에서 쿠폰 코드를 적용하기.
계약(작고 테스트 가능한): 카트 소계(센트 단위)와 선택적 쿠폰을 받아 최종 합계를 센트 단위로 반환한다. 규칙: 퍼센트 쿠폰은 센트 단위로 내림(round down), 고정 쿠폰은 고정 금액을 빼고, 합계는 절대 0 미만이 될 수 없다. 쿠폰은 무효, 만료, 이미 사용되었을 수 있다.
단순히 applyCoupon()에 대한 테스트를 요청하지 마세요. 대신 이 계약에 묶인 경계 케이스, 실패 모드, 불변성을 요구하세요.
수학이나 검증을 깨뜨리는 입력을 고르세요: 빈 쿠폰 문자열, subtotal = 0, 최소 구매액 바로 위/아래, 고정 할인액이 소계보다 큰 경우, 33% 같은 퍼센트로 반올림이 발생하는 경우.
쿠폰 조회가 실패하거나 상태가 잘못되었을 수 있다고 가정하세요: 쿠폰 서비스가 다운, 쿠폰이 만료, 사용자에 의해 이미 사용됨. 테스트는 이후 동작(쿠폰 거부와 명확한 오류, 합계 변경 없음)을 증명해야 합니다.
최소한의 고신호 테스트 세트(5개)와 각 테스트가 잡는 것:
이 테스트들이 통과하면 중복된 해피-패스 테스트로 스위트를 채우지 않고도 일반적인 취약점을 커버한 것입니다.
모델이 생성한 결과를 수락하기 전에 빠른 품질 점검을 하세요. 목표는 각 테스트가 특정하고 그럴듯한 버그로부터 여러분을 보호하는 것입니다.
체크리스트:
생성 후의 실용적 팁: 테스트 이름을 “should <동작> when <엣지 조건>” 또는 “should not <나쁜 결과> when <실패>”로 바꾸세요. 깔끔하게 이름을 바꿀 수 없다면 초점이 흐려진 것입니다.
Koder.ai로 빌드한다면 이 체크리스트는 스냅샷과 롤백 워크플로와 잘 맞습니다: 테스트를 생성하고 실행하고, 새 세트가 노이즈를 추가하면서 커버리지를 개선하지 못하면 롤백하세요.
프롬프트를 일회성으로 사용하지 말고 재사용 가능한 하니스로 취급하세요. 경계, 실패 모드, 불변성을 강제하는 블루프린트 프롬프트 하나를 저장하고 새 함수, 엔드포인트, UI 흐름마다 재사용하세요.
빠르게 결과를 개선하는 간단한 습관: 각 테스트가 어떤 버그를 잡을지 한 문장으로 적게 하세요. 그 문장이 일반적이면 그 테스트는 아마 소음입니다.
제품 도메인 불변성의 살아있는 목록을 유지하세요. 머릿속에 두지 말고 저장하세요. 실제 버그를 발견할 때마다 추가하세요.
가볍게 반복할 수 있는 워크플로:
채팅으로 앱을 빌드한다면 이 사이클을 Koder.ai(koder.ai) 내부에서 실행하세요. 계약, 계획, 생성된 테스트가 한곳에 남습니다. 리팩터링으로 동작이 예기치 않게 바뀌면 스냅샷과 롤백으로 비교하여 고신호 세트가 안정될 때까지 반복하세요.
기본 권장: 실제 버그를 잡을 수 있는 작은 집합을 목표로 하세요.
짧게 말하면 보통 함수(모듈)당 6–10개의 테스트가 좋은 기준입니다. 더 필요하면 보통 그 단위(unit)가 너무 많은 일을 하거나 계약이 불명확하다는 신호입니다.
해피-패스 테스트는 주로 예제가 여전히 동작한다는 것만 증명합니다. 운영에서 깨지는 부분을 놓치는 경향이 큽니다.
고신호 테스트는 다음을 목표로 합니다:
먼저 짧은 계약(tiny contract) 을 적어보세요. 한 번에 숨을 고르며 읽을 수 있을 정도로 간결해야 합니다:
그 계약에서 테스트를 생성하세요. 단순 예제만으로 묻지 마세요.
우선 테스트할 가치가 높은 경계부터 테스트하세요:
입력 차원별로 하나 또는 두 개만 골라서 각 테스트가 고유한 위험을 다루게 하세요.
좋은 실패 모드 테스트는 두 가지를 증명합니다:
DB 쓰기가 관여하면 실패 후 저장소 상태를 항상 확인하세요.
기본 접근: 불변성을 관찰 가능한 결과로 변환해 assertion을 작성하세요.
예:
expect(total).toBeGreaterThanOrEqual(0)반환값과 부작용을 모두 확인하는 것을 선호하세요. 많은 버그가 “정상 응답이지만 잘못된 쓰기” 형태로 숨어 있습니다.
해피-패스 테스트는 불변성을 보호하거나 중요한 통합을 검증할 때 유지할 가치가 있습니다.
유지할 만한 이유:
그렇지 않다면 더 많은 클래스의 버그를 잡는 경계/실패 테스트로 교체하세요.
모델에게 PHASE 1: 계획만 먼저 요구하세요.
요구사항:
계획을 승인한 다음에만 코드 생성을 요청하세요. 이렇게 하면 "거의 같은 테스트 20개" 같은 결과를 피할 수 있습니다.
기본 원칙: 소유하지 않은 경계(DB/네트워크/시계)만 모킹하고 나머지는 실제로 두세요.
과도한 모킹을 피하려면:
리팩터링 후 동작이 변하지 않았는데 테스트가 깨지면 보통 과도한 모킹이나 구현 의존성 때문입니다.
간단한 삭제 테스트를 사용하세요:
중복 확인도 하세요: