--- id: testing-mutation-testing title: Mutation Testing — Stryker / 테스트 품질 측정 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [testing, mutation, stryker, vibe-coding] tech_stack: { language: "TS / Stryker", applicable_to: ["Backend", "Frontend"] } applied_in: [] aliases: [mutation testing, Stryker, mutation score, killed mutant, surviving mutant] --- # Mutation Testing > Coverage 100% 도 거짓 — 진짜 검증은 **mutation testing**. 코드 변형 (mutant) → 테스트 fail 해야. 안 fail = 테스트가 의미 없음. Stryker (TS), PIT (Java), mutmut (Python). ## 📖 핵심 개념 - Mutant: 한 줄 변형 (`+` → `-`, `<` → `<=`). - Killed: 어떤 테스트가 실패 (테스트 효과 OK). - Survived: 모든 테스트 통과 (테스트 부족). - Mutation score: killed / total. ## 💻 코드 패턴 ### Stryker setup ```bash yarn add -D @stryker-mutator/core @stryker-mutator/vitest-runner @stryker-mutator/typescript-checker ``` ```json // stryker.config.json { "$schema": "https://raw.githubusercontent.com/stryker-mutator/stryker-js/master/packages/core/schema/stryker-schema.json", "testRunner": "vitest", "checkers": ["typescript"], "mutate": ["src/**/*.ts", "!src/**/*.test.ts"], "thresholds": { "high": 80, "low": 60, "break": 50 }, "concurrency": 4, "reporters": ["progress", "html", "clear-text"] } ``` ```bash yarn stryker run # HTML report → reports/mutation/index.html ``` ### Mutant 예시 ```ts // 원본 function isAdult(age: number): boolean { return age >= 18; } // Mutants: // 1. age > 18 ← edge case 차이 // 2. age <= 18 // 3. age == 18 // 4. !(age >= 18) // 5. true / false 직접 return // 만약 모든 테스트가 통과 = 테스트 부실 test('adult', () => expect(isAdult(20)).toBe(true)); // mutant 1 통과 (20 > 18) // → 18 도 테스트 추가 test('exactly 18', () => expect(isAdult(18)).toBe(true)); test('17 not adult', () => expect(isAdult(17)).toBe(false)); ``` ### 결과 해석 ``` Survived mutants: src/auth.ts:42 — `if (token)` → `if (true)` survived → 테스트 가 token 없는 case 검사 안 함 Killed: src/sum.ts:5 — `+` → `-` killed by sum.test.ts ``` ### Time / cost (큰 단점) ``` 1000개 mutant × 각 test suite 실행 = 매우 느림 (시간 단위). ``` 해결: - **Differential**: 변경된 파일만. - **Concurrency**: 병렬 worker. - **Filter**: 중요 모듈만 (auth, payment). ```bash stryker run --since main ``` ### CI 통합 (incremental) ```yaml - name: Mutation test (PR diff) run: yarn stryker run --since origin/main continue-on-error: false # 점수 < 50 = 실패 ``` ### 의도된 surviving (ignore) ```ts // stryker-disable-next-line StringLiteral const ENV = 'production'; // 또는 function noop(): void { // stryker-disable-line all } ``` ### Property + mutation 조합 ``` Property test 가 random input 100개 → mutant 잡기 강력. 일반 unit test 보다 mutation score 높음. ``` ### Frontend mutation ```ts // React 컴포넌트 function Button({ disabled, onClick }: ...) { return ; } // Mutant: disabled={!disabled} // 테스트: