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

4.4 KiB
Raw Blame History

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
testing-mutation-testing Mutation Testing — Stryker / 테스트 품질 측정 Coding draft B conceptual 2026-05-09 2026-05-09
testing
mutation
stryker
vibe-coding
language applicable_to
TS / Stryker
Backend
Frontend
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

yarn add -D @stryker-mutator/core @stryker-mutator/vitest-runner @stryker-mutator/typescript-checker
// 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"]
}
yarn stryker run
# HTML report → reports/mutation/index.html

Mutant 예시

// 원본
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).
stryker run --since main

CI 통합 (incremental)

- name: Mutation test (PR diff)
  run: yarn stryker run --since origin/main
  continue-on-error: false  # 점수 < 50 = 실패

의도된 surviving (ignore)

// 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

// 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 와 함께 = 강력.

🔗 관련 문서