docs(10_Wiki): 위키 전체 재구성 — Topic_* 폴더를 4개 카테고리로 통합 + 대규모 중복 제거
Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를 Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류. - 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로 자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거, 동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거. - 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming, Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business, Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로, 나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는 title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백). 원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지. - 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서. - 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는 지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지. - Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경. - 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
---
|
||||
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]]
|
||||
Reference in New Issue
Block a user