우리는 코드의 품질을 높이기 위해 열심히 단위 테스트를 작성하고, 코드 커버리지 리포트를 보며 안도감을 느낍니다. "코드 커버리지 90% 달성! 이제 우리 코드는 안전하겠지?"
하지만 정말 그럴까요? 여기 코드 커버리지 100%를 달성하는 테스트가 있습니다.
// Calculator.java
public int add(int a, int b) {
return a + b;
}
// CalculatorTest.java
@Test
void testAdd() {
Calculator calculator = new Calculator();
calculator.add(1, 1); // 실행은 했지만...
assertTrue(true); // 결과 검증은 하지 않는다!
}
이 테스트는 add
메서드의 모든 라인을 실행하므로 코드 커버리지는 100%입니다. 하지만 만약 개발자가 return a - b;
로 잘못 코딩했더라도, 이 테스트는 여전히 성공할 것입니다. 즉, 버그를 전혀 잡지 못하는 '무의미한 테스트'인 셈입니다.
이처럼 코드 커버리지는 테스트의 '양'을 측정할 뿐, '질'을 보장하지 못합니다. 그렇다면, "내 테스트가 정말로 버그를 잘 잡는지" 어떻게 증명할 수 있을까요? 그 해답이 바로 뮤테이션 테스팅(Mutation Testing)에 있습니다.
뮤테이션 테스팅이란, "당신의 테스트 코드가 얼마나 훌륭한지"를 테스트하는 방법입니다.
조금 더 구체적으로는, 운영 코드에 의도적으로 작은 버그(Mutation, 돌연변이)를 주입한 뒤, 기존의 테스트 스위트가 이 버그를 잡아내는지(테스트가 실패하는지) 확인하는 기법입니다.
마치 백신을 개발하는 과정과 같습니다. 약화된 바이러스(돌연변이)를 우리 몸(테스트 스위트)에 주입했을 때, 면역 체계(테스트 케이스)가 이를 감지하고 항체를 만들어내는지를 확인하는 것입니다.
>
를 >=
로, +
를 -
로 바꾸기, if
문 조건 뒤집기 등)(죽인 돌연변이체 수 / 전체 돌연변이체 수) * 100
. 이 점수가 높을수록 테스트 스위트의 품질이 높음을 의미합니다.가장 널리 쓰이는 Java용 뮤테이션 테스팅 도구인 Pitest를 예로 들어보겠습니다.
// AgeValidator.java
public class AgeValidator {
public boolean isAdult(int age) {
return age >= 19;
}
}
// AgeValidatorTest.java
@Test
void 스무살은_성인이다() {
AgeValidator validator = new AgeValidator();
assertTrue(validator.isAdult(20));
}
이 테스트는 isAdult
메서드를 실행하므로 구문/분기 커버리지는 100%입니다.
Pitest를 실행하면, 다음과 같은 돌연변이체들을 자동으로 생성하고 테스트를 다시 실행합니다.
return age > 19;
(>=
를 >
로 변경)return age <= 19;
(>=
를 <=
로 변경)return false;
(무조건 false 반환)Pitest 리포트를 확인하면 다음과 같은 결과를 볼 수 있습니다.
isAdult(20)
테스트가 이 돌연변이 코드에서는 실패하기 때문입니다.isAdult(20)
테스트는 20 > 19
도 참이므로, 이 돌연변이 코드에서도 여전히 성공합니다. 즉, 우리의 테스트는 >=
와 >
의 차이를 구분하지 못하는 '약한 테스트'였던 것입니다.생존한 돌연변이를 죽이기 위해, 우리는 경계값 테스트를 추가해야 합니다.
// AgeValidatorTest.java
@Test
void 열아홉살은_성인이다() {
AgeValidator validator = new AgeValidator();
assertTrue(validator.isAdult(19));
}
이 테스트 케이스를 추가하고 다시 Pitest를 실행하면, Mutant 1
(return age > 19;
)은 isAdult(19)
호출 시 false
를 반환하여 테스트에 실패하게 됩니다. 드디어 Mutant 1도 KILLED되고, 우리의 뮤테이션 점수는 올라갑니다.
장점:
과제:
i++
를 ++i
로)는 영원히 죽일 수 없습니다. 이런 것들은 사람이 직접 분석하여 제외해야 할 수 있습니다.뮤테이션 테스팅은 테스트의 '양'을 측정하는 코드 커버리지를 넘어, 테스트의 '질'을 측정하는 궁극의 기술입니다. 단순히 코드를 '실행'하는 것을 넘어, 우리의 테스트가 정말로 '결함을 잘 잡아내는지'를 증명해 줍니다.
매일 CI 파이프라인에서 실행하기는 부담스러울 수 있지만, 주기적으로 뮤테이션 점수를 측정하고 관리하는 것은 우리 팀의 테스트 스위트가 시간이 지나도 녹슬지 않고 계속해서 날카롭게 유지되도록 돕는 가장 강력한 건강 진단 도구가 될 것입니다.
#소프트웨어테스팅
#뮤테이션테스팅
#단위테스트
#테스트품질
#코드커버리지
#Pitest
#Stryker