🧬 MutantHunter
테스트가 못 잡는 구멍을 찾아 테스트를 만들고,
실제로 버그를 잡는지 실행으로 증명한 것만 PR에 제안합니다.
왜 만들었나
커버리지는 “그 줄이 실행됐는가”만 봅니다. “그 줄이 틀렸을 때 누가 알아채는가”는 묻지 않습니다.
export function grade(score: number) {
if (score >= 90) return "A"; // 테스트가 이 줄을 실행은 함
return "F";
}
grade(95)만 테스트하면 커버리지는 올라가지만,
누가 >= 90을 >= 10으로 바꿔도
아무 테스트도 실패하지 않습니다.
그런 지점을 찾아 메우는 도구입니다.
동작
PR diff → 변경된 코드를 일부러 망가뜨림 (뮤테이션)
→ 테스트 재실행
→ 전부 통과? = 그 코드는 틀려도 아무도 모른다
→ LLM이 그 구멍을 막는 테스트 생성
→ 5개 게이트로 검증
→ 통과한 것만 PR 코멘트
핵심은 세 번째 게이트입니다. 현재 코드에서 통과하는 그럴듯한 테스트는 얼마든지 만들어집니다. 실측에서 뮤턴트 102개를 돌렸을 때 생성 테스트 157건을 폐기했고, 그중 53건이 “통과하지만 결함은 못 잡는” 테스트였습니다. 코드 리뷰로는 걸러내기 어렵습니다.
언제 값을 내는가
| 대상 | 뮤테이션 스코어 | 채택 |
|---|---|---|
unjs/ufo | 88% | 0 / 3 |
unjs/destr | 86% | 0 / 7 |
antfu/utils | 0% | 6 / 7 |
이미 잘 테스트된 코드에는 제안할 것이 없고, 그때 침묵하는 것이 올바른 동작입니다. 테스트가 얇은 코드에서 값을 냅니다.
쓰는 법
대상 저장소에 커밋되는 것은 워크플로우 파일 하나뿐입니다.
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- uses: PYSoYa/mutant-hunter@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
env:
MH_PROVIDER: mistral
MH_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
TypeScript / JavaScript · vitest 또는 jest · 무료 API 키 하나 (Gemini · Groq · Mistral · OpenRouter)
만들면서 배운 것
- 측정 장치를 먼저 만들었습니다. 잡음 폭을 재고 나서 앞선 A/B 판정 두 개를 스스로 철회했습니다.
- 프롬프트 튜닝은 효과를 입증하지 못했습니다. 세 번 시도했고 전부 잡음 범위였습니다. 대신 대상 선택이 3배 차이를 만들었습니다.
- 외부 저장소에 처음 돌리자 자체 환경에서 가려져 있던 버그 3건이 드러났습니다.
측정 과정과 그때마다 틀린 것은 측정 기록에 남겼습니다.