6. Mutation Score 해석 가이드
1. 목표 점수 가이드라인
| 프로젝트 유형 | 권장 Mutation Score | 근거 |
|---|---|---|
| 핵심 비즈니스 로직 | 90%+ | 금융 계산, 권한 검증 등 버그 비용이 높은 코드 |
| 일반 애플리케이션 코드 | 80%+ | 대부분의 서비스 로직 |
| 유틸리티 / 헬퍼 | 70%+ | 단순한 변환, 포맷팅 등 |
| DTO / 데이터 모델 | 측정 불필요 | getter/setter만 있는 클래스는 돌연변이 테스트 대상에서 제외 |
Mutation Score 100%를 맹목적으로 추구할 필요는 없음
100%를 달성하기 위해 의미 없는 테스트를 추가하는 것은 오히려 유지보수 비용만 높임
2. Survived Mutant를 그대로 허용해도 되는 경우
모든 Survived Mutant가 테스트의 결함을 의미하는 것은 아님
다음 경우에는 생존이 허용될 수 있음
동치 돌연변이 (Equivalent Mutant)
변이된 코드가 원본과 동작이 완전히 동일한 경우
어떤 테스트를 작성해도 이 돌연변이를 잡을 수 없음
// 원본
int index = 0;
while (index < list.size()) {
process(list.get(index));
index++;
}
// 변이: index < list.size() → index != list.size()
// index가 0부터 1씩 증가하므로 < 와 != 의 결과가 항상 동일 → 동치 돌연변이동치 돌연변이는 돌연변이 테스트의 알려진 한계
Pitest는 이를 자동으로 식별하지 못하므로, 리포트에서 수동으로 판단해야 함
로깅 / 디버그 코드
// 로그 출력이 변이되어도 비즈니스 로직에 영향 없음
logger.info("주문 생성: " + orderId);3. 흔한 함정
함정 1: Mutation Score만 보고 라인 커버리지를 무시
Line Coverage: 60% | Mutation Score: 95%Mutation Score가 높아 보이지만, 테스트가 커버하지 않는 40%의 코드에는
돌연변이가 생성되지도 않았음
→ 커버리지가 없는 코드는 측정 대상에서 빠짐
Mutation Score는 테스트가 커버하는 코드 내에서의 품질 지표로,
라인 커버리지가 낮으면 Mutation Score가 높아도 의미가 제한적임
따라서 두 지표를 함께 봐야 함
함정 2: 테스트 수를 늘리면 Mutation Score가 올라간다는 착각
// 테스트 10개를 추가했지만 모두 같은 패턴
@Test void test1() { assertEquals("A", classify(95)); }
@Test void test2() { assertEquals("A", classify(96)); }
@Test void test3() { assertEquals("A", classify(97)); }
// ... 경계값(90)은 여전히 테스트하지 않음 → Mutation Score 변화 없음테스트의 수가 아니라 다양성이 중요
→ 경계값, 예외 경로, 다른 분기를 커버하는 테스트를 추가해야 함
함정 3: Mutation Score를 CI에서 강제하면서 임계값을 너무 높게 설정
<!-- 너무 공격적인 설정 -->
<mutationThreshold>100</mutationThreshold>동치 돌연변이 때문에 100%는 현실적으로 달성 불가능한 경우가 많음
빌드가 계속 실패하면 팀이 돌연변이 테스트 자체를 포기하게 됨
CI에서는 80~85% 정도의 임계값으로 시작하고,
팀이 익숙해지면 점진적으로 올리는 것을 권장
4. Pitest 리포트 활용 워크플로우
- Pitest를 실행
- 리포트에서 빨간색 라인(Survived Mutant)을 찾음
- 해당 라인의 돌연변이 상세를 확인
- 판단:
- 테스트를 추가해야 하는가? → 테스트 작성
- 동치 돌연변이인가? → 무시 (주석으로 기록)
- 테스트 불가능한 코드인가? →
excludedMethods로 제외
- 다시 실행하여 개선 여부 확인
5. Pitest 설정 옵션 참고
| 옵션 | 설명 | 예시 |
|---|---|---|
targetClasses | 변이 대상 클래스 | com.example.service.* |
targetTests | 사용할 테스트 클래스 | com.example.service.*Test |
mutators | 활성화할 Mutator | DEFAULTS, ALL |
excludedMethods | 제외할 메서드 | toString, hashCode |
mutationThreshold | 최소 Mutation Score (CI용) | 80 |
timestampedReports | 타임스탬프 디렉토리 생성 | false |
outputFormats | 리포트 형식 | HTML, XML |
<configuration>
<targetClasses>
<param>com.example.service.*</param>
</targetClasses>
<excludedMethods>
<param>toString</param>
<param>hashCode</param>
<param>equals</param>
</excludedMethods>
<mutationThreshold>80</mutationThreshold>
</configuration>6. Pitest 요약
돌연변이 테스트는 테스트의 품질을 객관적으로 측정하는 유용한 기법이지만,
명확한 한계점도 존재하기 때문에 숫자 지표에만 집착하기보다는, Survived Mutant가 알려주는 테스트의 빈틈에 집중하는 것이 중요