# Testing — 기능 동작이 아니라 변경의 파급(회귀)을 테스트한다

적용: `*.spec.ts(x)` 작성 시. "이 기능이 동작하는가?"가 아니라 **"이 변경이 다른 곳을 깨뜨리는가?"**를 묻는다.

## 테스트할 것 — 반복 버그 5부류 (실증된 패턴)

1. **서버 실패 → UI 불일치**: mutation 실패 시 낙관적 갱신이 롤백되는가, stuck 없이 실패가 표시되는가. **롤백을 단언했으면 재시도 경로도 같이 단언한다** — 폼에서 선택을 저장 전 값으로 되돌리면 `isDirty`가 꺼지며 저장 버튼이 잠겨, "다시 시도해주세요"라고 안내하면서 재시도할 대상을 없애는 함정이 생긴다(실측: 코드 리뷰와 화면 심사가 독립적으로 같은 결함을 지적). 실패 케이스에서 볼 것은 "에러 문구가 떴나"가 아니라 **"화면이 갇히지 않았나"**다.
2. **동시 조작 간섭**: A 진행 중 B를 조작하면 서로 간섭하지 않는가.
3. **상태 전환의 크로스 도메인 파급**: X를 바꿨을 때 연관 목록·카운트가 함께 갱신되는가 (invalidate 검증).
4. **비동기 stuck**: 실패 variant가 없어 영원히 로딩에 갇히는 경로.
5. **포커스·입력 유실**: 리렌더로 입력 중 값·포커스가 날아가는 경로.

## 테스트하지 않는 것

- 렌더링 존재 확인("버튼이 보인다") — 화면 검증은 ui-critic + 사용자 몫.
- 도구가 보장하는 영역 — zod safeParse 결과, tsc가 막는 타입 오류.
- 도메인 분기 로직은 컴포넌트 밖 순수 함수로 추출한 뒤 **그 함수를** 테스트한다.

기존 룰 유지: Vitest + RTL, 콜로케이션, `.spec.ts(x)`, API는 `vi.mock`(실서버 호출 금지 — SamplePage.spec 참조).

무엇을 테스트할지 정한 다음 **어떻게 쓰는지**(유형별 템플릿·문장 규칙·matcher)는 `.claude/skills/write-test/SKILL.md`.
