테스트 커버리지 — JaCoCo로 측정하고 해석하기
커버리지는 테스트가 코드를 얼마나 실행했는지 보여 주는 지표입니다. JaCoCo의 계측 원리, 라인·브랜치 커버리지의 차이, 빌드 게이트 설정, 그리고 커버리지 숫자를 맹신하면 안 되는 이유까지 정리합니다.
지난 글에서 아키텍처 규칙을 테스트로 강제하는 법을 살펴봤습니다. 테스트를 열심히 작성하다 보면 자연스레 떠오르는 질문이 있습니다. “우리 테스트는 코드를 얼마나 검증하고 있을까?” 이 물음에 숫자로 답하는 것이 테스트 커버리지입니다. 자바 진영에서 사실상 표준 도구는 JaCoCo(Java Code Coverage)입니다. 이번 글에서는 JaCoCo가 어떻게 커버리지를 재는지, 그 숫자를 어떻게 읽고 어디까지 믿어야 하는지 정리합니다.
JaCoCo는 어떻게 측정하는가
JaCoCo는 바이트코드 계측(instrumentation) 방식으로 동작합니다. 자바 에이전트가 클래스 로딩 시점에 바이트코드에 작은 “프로브”를 삽입하고, 테스트가 실행되며 어떤 프로브가 지나갔는지 jacoco.exec 파일에 기록합니다. 테스트가 끝나면 이 실행 기록과 원본 클래스를 대조해 어떤 라인·분기가 실행됐는지 계산하고, HTML이나 XML 리포트로 출력합니다. 소스 수정 없이 바이트코드만 건드리므로, 기존 테스트를 그대로 두고 커버리지를 잴 수 있습니다.
커버리지의 여러 종류
가장 흔히 보는 숫자는 라인 커버리지지만, 사실 이것은 가장 느슨한 지표입니다. 분기가 있는 코드를 생각해 봅시다.
int classify(int score) {
if (score >= 60) {
return PASS;
}
return FAIL;
}
classify(80) 하나만 테스트하면 if 라인과 return PASS를 실행하므로 라인 커버리지는 높게 나오지만, score < 60인 경우는 한 번도 검사하지 않았습니다. 브랜치 커버리지는 if의 참·거짓 양쪽이 모두 실행됐는지를 보기 때문에, 이런 누락을 잡아냅니다. 그래서 라인 커버리지보다 브랜치 커버리지를 더 신뢰할 만한 지표로 봅니다. 메서드·클래스 커버리지는 “아예 한 번도 호출되지 않은 코드”라는 거시적 사각지대를 찾는 데 유용합니다.
Gradle 설정과 빌드 게이트
JaCoCo 플러그인을 적용하고 리포트 태스크를 테스트에 연결합니다.
plugins {
id 'jacoco'
}
jacocoTestReport {
dependsOn test
reports {
html.required = true
xml.required = true
}
}
숫자를 측정하는 데 그치지 않고, 일정 기준에 미달하면 빌드를 실패시키는 게이트를 걸 수 있습니다.
jacocoTestCoverageVerification {
violationRules {
rule {
limit {
counter = 'BRANCH'
minimum = 0.70
}
}
}
}
check.dependsOn jacocoTestCoverageVerification
이렇게 하면 브랜치 커버리지가 70% 아래로 떨어질 때 check(따라서 CI)가 실패합니다. 새 코드가 테스트 없이 슬며시 들어오는 것을 막는 안전장치입니다.
숫자를 맹신하지 말 것
커버리지는 강력한 도구지만, 목표가 아니라 신호입니다. 100% 커버리지가 버그 없음을 보장하지는 않습니다. 다음 테스트를 봅시다.
@Test
void classify_실행만_한다() {
classify(80);
classify(50); // 단언이 하나도 없다
}
이 테스트는 두 분기를 모두 실행해 브랜치 커버리지를 100%로 만들지만, 아무것도 검증하지 않습니다. 커버리지는 “어떤 코드가 실행됐는가”만 셀 뿐, “결과가 옳은지 단언했는가”는 보지 못합니다. 그래서 높은 커버리지를 강제하면, 단언 없이 숫자만 채우는 가짜 테스트가 늘어나는 부작용이 생기기도 합니다.
커버리지는 “어디가 전혀 테스트되지 않았는가”를 찾는 데 쓰는 것이 가장 건강합니다. 0%인 핵심 도메인 클래스를 발견하는 데는 유용하지만, 95%를 99%로 끌어올리는 데 집착하는 것은 대개 헛수고입니다. 이 “실행은 하지만 검증은 안 하는” 약점을 정면으로 공격하는 기법이 바로 다음 글의 뮤테이션 테스트입니다.
정리
JaCoCo는 바이트코드 계측으로 테스트가 실행한 라인과 분기를 기록해 커버리지를 측정합니다. 라인 커버리지보다 브랜치 커버리지가 더 의미 있는 지표이며, 빌드 게이트로 최소 기준을 강제해 회귀를 막을 수 있습니다. 다만 커버리지는 실행 여부만 셀 뿐 단언의 품질은 보지 못하므로, 숫자 자체를 목표로 삼기보다 사각지대를 찾는 신호로 활용하는 것이 옳습니다. 다음 글에서는 이 한계를 보완하는 뮤테이션 테스트를 다룹니다.
지난 글: ArchUnit — 아키텍처를 테스트로 강제하기
다음 글: 뮤테이션 테스트 — PIT로 테스트의 품질을 검증하기
읽어주셔서 감사합니다. 😊