test-code-guide
테스트 코드 작업 스킬. 작성 전 설계 점검(무엇을 어느 레벨에서 어떤 기법으로), 작성 중 품질 규범, 기존 테스트 리뷰 3모드를 지원하고 구현 미러링, 무의미한 커버리지 채우기, 비결정성, 테스트 간 결합을 막는다. "테스트 짜줘", "테스트 코드 작성해줘", "이 함수 테스트 추가", "unit test 추가", "통합 테스트 짜줘", "E2E 테스트
Files3
View on GitHubSKILL.mdskills/test-code-guide/SKILL.md
GitHubTest Code Guide
테스트 코드 작업에서 커버리지 숫자가 아니라 행동 검증을 산출물로 만드는 스킬. 특정 테스트 프레임워크, assertion 라이브러리, 디렉토리 관례를 강제하지 않는다. 대상 repo의 기존 테스트를 먼저 읽는다.
이 스킬이 막는 실패
Agents는 테스트를 "구현 완료 후 붙이는 산출물"로 보고 통과하는 테스트부터 만들기 쉽다. 이 스킬은 다음 실패를 막는 데 집중한다.
- 구현 미러링: 구현 로직을 복제한 기대값 계산, 내부 호출 순서를 고정하는 mock 검증. behavior가 같아도 리팩토링마다 깨진다.
- 무의미한 커버리지: assertion이 없거나 약한 테스트, happy path만 있는 스위트, 숫자를 채우기 위한 테스트.
- 비결정성·느린 테스트: sleep·실제 시간·실제 네트워크·외부 상태 의존, flaky를 재시도로 덮기, unit으로 검증 가능한 것을 상위 레벨에서 돌리는 느린 스위트.
- 테스트 간 결합: 실행 순서 의존, 공유 fixture/DB 오염, 다른 테스트의 산출물에 의존하는 데이터.
모드 선언
요청 의도에 따라 모드를 정하고 시작한다.
- 설계 점검 (plan 보조): 테스트 작성 전 대상 behavior, 레벨, 기법, coverage 기준을 결정한다. plan에 빠진 결정을 보강한다.
- 작성: 결정된 기준에 따라 테스트 코드를 작성한다. 작성 규범은 아래 체크리스트와 references를 따른다.
- 리뷰: 기존 테스트가 4대 실패에 걸리는지 점검한다. read-only — 발견 보고까지가 기본이고 수정은 명시 요청이 있을 때만.
모드가 바로 안 잡히는 요청의 진입점:
- 커버리지 개선: 리뷰 모드로 gap을 확인하고, 커버 안 된 behavior를 도출해 작성 모드로 전환한다.
- 테스트 전략: 설계 점검을 주요 behavior 단위로 반복하고, 레벨 배분·CI 배치는 references를 따른다.
- TDD: 작성 모드로 실패하는 테스트를 먼저 쓰고 구현과 교대한다. Gotchas의 red 항목을 지킨다.
이 스킬은 테스트 생성기가 아니다. 실제 코드는 시작 절차 1에서 확인한 repo 관례를 따라 작성한다.
시작 절차
1은 모든 모드 공통이다. 2-5는 설계 점검·작성 모드용이고, 리뷰 모드는 1 다음 Review 질문과 High-Signal Checklist로 바로 간다.
기존 테스트 관례를 확인한다.
- 같은 repo의 유사 대상 테스트를 먼저 읽는다. 러너, assertion 스타일, fixture/헬퍼, 명명, 디렉토리 배치, CI 실행 방법을 실제 코드에서 확인한다.
- 테스트 인프라가 없으면 그 사실을 보고한다. 러너 설치·설정은 별도 요청으로 다룬다.
대상 behavior를 정의한다.
- 테스트 단위는 구현 함수가 아니라 검증할 행동(계약)이다. 요구사항, acceptance criteria, 버그 재현 조건에서 도출한다.
- behavior를 문장으로 쓸 수 없으면 아직 테스트를 쓸 준비가 안 된 것이다.
레벨을 결정한다.
- 그 behavior를 검증할 수 있는 가장 낮은 레벨을 고른다. unit에서 검증 가능한 로직을 E2E로 올리지 않는다.
- 레벨 배분 판단이 필요하면
references/test-design-techniques.md의 레벨 배분 섹션을 읽는다.
기법과 coverage 기준을 고른다.
- 입력 공간이 넓거나 조합·상태·경계가 걸리면
references/test-design-techniques.md를 읽고 기법을 선택한다. - 단순 로직이면 기법 없이 positive → negative 순서로 케이스를 나열해도 된다.
- 입력 공간이 넓거나 조합·상태·경계가 걸리면
작성 규범을 적용한다.
- fixture, oracle, 비동기 대기, 테스트 데이터 설계가 걸리면
references/automation-quality.md를 읽는다.
- fixture, oracle, 비동기 대기, 테스트 데이터 설계가 걸리면
결정 기록 포맷
설계 점검 모드와 plan 보조에서는 아래 정도의 짧은 결정을 남긴다. 긴 코드 예시는 쓰지 않는다.
대상 behavior: 검증할 행동 한 문장 (구현 함수명이 아니라 계약)
Test level: unit | integration | system/E2E — 선택 이유 한 줄
기법/coverage: 예) EP + 2-value BVA | pairwise | state transition 0-switch | 기법 없음
Oracle: 기대값 산출 방법 — 독립 계산 | property | metamorphic relation | 근거 있는 고정 스냅샷
Test data: 인라인 | 외부화 | 생성 — 격리 방법
케이스 순서: positive → negative → (해당 시) 경계·예외·non-functional
High-Signal Checklist
구현 미러링 방지
- 테스트가 public behavior를 검증하는가, 내부 구현 단계를 고정하는가.
- 기대값을 구현과 같은 로직으로 계산하지 않는가. oracle은 구현과 독립적으로 산출한다.
- mock 검증이 협력자와의 계약(무엇을 요청하는가)인가, 구현 순서 복제(몇 번 어떤 순서로 불렀는가)인가.
- mock은 경계(네트워크, 시간, 외부 서비스, 파일시스템)만 대체하는가. 저렴하고 결정적인 협력자는 실물을 쓰고, SUT 내부를 mock하지 않는다.
- behavior가 같은 리팩토링 후에도 이 테스트는 통과하는가.
커버리지 실질화
- assertion 없는 테스트는 없는가. 실행만 하고 결과를 확인하지 않는 테스트는 커버리지만 올린다.
- 같은 equivalence partition의 값을 반복 테스트하지 않는가. partition당 대표값 + 경계값이면 충분하다.
- negative 케이스(잘못된 입력, 예외 경로)가 있는가. positive만 있는 스위트는 절반이다.
- 커버리지 숫자는 gap 탐지 도구로 쓴다. 낮은 영역에 테스트를 추가하되, 숫자 자체를 목표로 삼지 않는다.
결정성·속도
- hard-coded wait(sleep)가 없는가. 폴링+timeout 또는 이벤트 대기로 바꾼다.
- 시간, 랜덤, 네트워크, 파일시스템 의존이 주입 또는 고정되는가. clock 주입, seed 고정·기록.
- 같은 커밋에서 반복 실행 시 결과가 항상 같은가.
- unit에서 검증 가능한 것을 상위 레벨에서 반복해 스위트를 느리게 만들지 않는가.
- flaky 테스트는 스위트에서 격리하고 원인을 분석한다. repo에 승인된 retry 정책이 없는 한 재시도 옵션으로 덮지 않는다.
격리
- 각 테스트가 단독 실행, 임의 순서 실행에서 통과하는가.
- 공유 상태(DB, 파일, 전역, 외부 서비스)는 setup에서 만들고 teardown에서 복원하는가.
- 테스트 데이터를 테스트가 소유하는가. 다른 테스트가 만든 데이터에 의존하지 않는다.
- 하나의 거대 테스트보다 작은 테스트 다수인가. 실패 지점 특정과 부분 실행이 가능해야 한다.
Review 질문
리뷰 시 아래 질문에 답하지 못하는 테스트는 수정 또는 삭제 후보다.
- 이 테스트가 잡으려는 결함은 무엇인가.
- 구현을 다시 짜도(behavior 동일) 이 테스트는 유효한가.
- 실패 메시지만 보고 무엇이 깨졌는지 알 수 있는가.
- happy path 외에 경계, 예외, negative 케이스가 있는가.
- 단독·역순 실행에도 통과하는가.
- sleep, 실제 시간, 실제 네트워크 의존이 있는가.
- 이 테스트가 실패하면 제품 결함인지 테스트 코드 결함인지 구분할 수 있는가.
Gotchas
- 깨진 테스트의 기대값을 갱신하기 전에 멈출 것: behavior 변경이 의도된 것인지 먼저 확인한다. 원인 분석 없이 기대값·스냅샷을 현재 출력에 맞추는 것은 회귀를 정답으로 승격시키는 행위다.
- 커버리지 % 목표에 기대지 말 것: 남는 N%는 가장 복잡한 — 즉 결함이 가장 많은 — 코드가 되기 쉽다. repo에 이미 coverage gate가 있으면 그 기준을 따르되, 새로 정할 때는 %가 아니라 명시적 제외 목록으로 관리한다.
- 실패하는 테스트를 skip으로 숨기지 말 것: 격리는 원인 분석과 함께만 한다. skip이 쌓인 스위트는 통과해도 아무것도 보장하지 않는다.
- 테스트 실패 ≠ 제품 결함: 원인 위치(제품/테스트 코드/환경/데이터) 구분은
references/automation-quality.md5절 기준으로 기록한다. - TDD 중의 red는 결함이 아니다: 구현 완료 전 실패하는 테스트를 defect처럼 다루거나, 급히 통과시키려고 assertion을 약화하지 않는다.
- tests wear out: 같은 회귀 스위트가 오래 한 번도 안 깨진다면 검출력이 닳았을 수 있다. 변경이 잦은 영역 기준으로 케이스를 갱신한다.
- 테스트 코드도 코드다: static analysis, 리뷰, 명명 규칙이 제품 코드와 동일하게 적용된다. "테스트라서 대충"이 유지보수 비용의 근원이다.
- 출력 문자열 단언이 fixture 이름에 걸려 거짓 통과한다: 대상이 임시 디렉토리나 케이스 이름을 진단 출력에 찍으면, 케이스 이름에 든 단어로
grep -q한 단언은 기능이 없어도 경로에서 매칭된다. 단언은 기능 고유 문구로 좁히고, 그 출력을 만드는 코드를 지운 변형으로 red가 나는지 한 번 실증한다. 구현 뒤에 쓴 단언은 특히 그렇다. - 러너 출력을
head로 자르지 않는다: vitest 같은 러너는 실행 중에 중간 프레임을 렌더한다. 수집 직후 프레임은Test Files N failed처럼 전량 실패로 보이고, 실제 결과는 그 뒤 최종 프레임에 나온다. 판정은 exit code나 마지막 요약(tail)으로 한다. 변경 규모와 안 맞는 전량 실패가 보이면 재실행하기 전에 출력을 끝까지 읽는다 — 재실행해서 "이번엔 통과"로 넘기면 flaky로 잘못 분류한다.
상세 참조
references/test-design-techniques.md: 무엇을 테스트할지 — 기법 선택 결정 트리, data/behavior/rule-based 기법과 coverage 기준, 화이트박스 보완, 레벨 배분. 설계 점검 모드나 케이스 도출이 막힐 때 읽는다.references/automation-quality.md: 어떻게 쓸지 — 테스트 케이스 품질 기준, fixture 격리, oracle/assertion, 비동기 대기, 테스트 데이터, E2E 패턴, 유지보수. 작성 모드에서 설계가 걸리거나 리뷰 심화 때 읽는다.