Files
2nd/10_Wiki/Topics/Coding/Testing_Mutation_Testing.md
T
2026-05-09 21:08:02 +09:00

166 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 <button disabled={disabled} onClick={onClick}>Click</button>;
}
// Mutant: disabled={!disabled}
// 테스트: <Button disabled={true} /> 가 disabled 인지 확인
```
### Score 목표
```
60% 이하: 테스트 부족
70-80%: 평균
80-90%: 좋음
90%+: 강 신뢰
100%: 보통 너무 비싸 — 80-90% 충분
```
## 🤔 의사결정 기준
| 적합 | 부적합 |
|---|---|
| Critical 모듈 (auth, payment) | 큰 codebase 전체 매번 |
| 알고리즘 / 순수 함수 | UI / E2E |
| 수학 / parser | 외부 부수효과 |
| 안전성 critical | Throwaway script |
| Property test 와 함께 | Snapshot test 만 |
## ❌ 안티패턴
- **Coverage 100% 만족**: 거짓 안전. mutation 으로 검증.
- **모든 코드 매번**: 시간 폭발. critical 모듈만 / incremental.
- **CI fail threshold 너무 높음 (95%)**: 깨지기 쉬움. 80% 시작.
- **Equivalent mutants 무시**: false positive. ignore 표시.
- **Snapshot test 만**: snapshot update 자동 통과 — mutation 안 잡음.
- **Mutation 점수만 보고 coverage 무시**: 둘 다 필요.
## 🤖 LLM 활용 힌트
- Stryker incremental + critical module.
- Survived mutant 보고 → 테스트 추가 / 코드 단순화.
- Property test 와 함께 = 강력.
## 🔗 관련 문서
- [[Testing_Property_Based]]
- [[Testing_Test_Pyramid]]
- [[Testing_Mocking_Boundaries]]