test-code-guide

테스트 코드 작업 스킬. 작성 전 설계 점검(무엇을 어느 레벨에서 어떤 기법으로), 작성 중 품질 규범, 기존 테스트 리뷰 3모드를 지원하고 구현 미러링, 무의미한 커버리지 채우기, 비결정성, 테스트 간 결합을 막는다. "테스트 짜줘", "테스트 코드 작성해줘", "이 함수 테스트 추가", "unit test 추가", "통합 테스트 짜줘", "E2E 테스트

SKILL.md

GitHub

Test 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로 바로 간다.

  1. 기존 테스트 관례를 확인한다.

    • 같은 repo의 유사 대상 테스트를 먼저 읽는다. 러너, assertion 스타일, fixture/헬퍼, 명명, 디렉토리 배치, CI 실행 방법을 실제 코드에서 확인한다.
    • 테스트 인프라가 없으면 그 사실을 보고한다. 러너 설치·설정은 별도 요청으로 다룬다.
  2. 대상 behavior를 정의한다.

    • 테스트 단위는 구현 함수가 아니라 검증할 행동(계약)이다. 요구사항, acceptance criteria, 버그 재현 조건에서 도출한다.
    • behavior를 문장으로 쓸 수 없으면 아직 테스트를 쓸 준비가 안 된 것이다.
  3. 레벨을 결정한다.

    • 그 behavior를 검증할 수 있는 가장 낮은 레벨을 고른다. unit에서 검증 가능한 로직을 E2E로 올리지 않는다.
    • 레벨 배분 판단이 필요하면 references/test-design-techniques.md의 레벨 배분 섹션을 읽는다.
  4. 기법과 coverage 기준을 고른다.

    • 입력 공간이 넓거나 조합·상태·경계가 걸리면 references/test-design-techniques.md를 읽고 기법을 선택한다.
    • 단순 로직이면 기법 없이 positive → negative 순서로 케이스를 나열해도 된다.
  5. 작성 규범을 적용한다.

    • fixture, oracle, 비동기 대기, 테스트 데이터 설계가 걸리면 references/automation-quality.md를 읽는다.

결정 기록 포맷

설계 점검 모드와 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.md 5절 기준으로 기록한다.
  • 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 패턴, 유지보수. 작성 모드에서 설계가 걸리거나 리뷰 심화 때 읽는다.