개념
커버리지 리포트가 세는 것
모듈 6(레슨 5)에서 커버리지를 "계획한 테스트 중 몇 %를 실행했는가"로 배웠다. 코드 수준의 커버리지 리포트는 조금 다른 질문에 답한다 — **"테스트를 실행했을 때, 코드의 몇 %가 실제로 한 번이라도 지나갔는가"**를 측정한다.
라인 커버리지(Line Coverage)
코드의 각 줄이 테스트 실행 중 한 번이라도 실행됐는지를 센다.
function isEligibleForFreeShipping(cartTotal, memberLevel) {
if (memberLevel === "VIP") { // 실행됨
return true // 실행됨
}
if (cartTotal >= 50000) { // 실행됨
return true // 실행 안 됨 ⚠
}
return false // 실행됨
}
test("VIP 회원은 무료배송이다", () => {
expect(isEligibleForFreeShipping(10000, "VIP")).toBe(true)
})이 테스트 하나만 돌려도 함수의 대부분 줄이 "실행됨"으로 표시되어 라인 커버리지가 높게 나온다 — 하지만 "장바구니 금액이 50,000원 이상일 때 무료배송이 되는지"는 전혀 검증되지 않았다.
브랜치 커버리지(Branch Coverage)
조건문의 각 분기(참/거짓 양쪽)가 모두 실행됐는지를 센다. 위
예시에서 cartTotal >= 50000이 참이 되는 경우가 한 번도 테스트되지
않았으므로, 브랜치 커버리지는 라인 커버리지보다 훨씬 정직하게
"이 조건은 검증 안 됐다"는 걸 드러낸다.
| 라인 커버리지 | 브랜치 커버리지 | |
|---|---|---|
| 측정 대상 | 코드 줄이 실행됐는가 | 조건의 각 분기가 모두 실행됐는가 |
| 함정 | 조건문이 있는 줄이 "실행됨"이어도, 한쪽 분기만 탄 것일 수 있다 | 라인 커버리지보다 더 엄격하지만, 여전히 "논리적으로 맞는 값"인지는 확인 안 함 |
100% 커버리지의 함정
브랜치 커버리지까지 100%를 채워도, 여전히 놓치는 것들이 있다.
test("cartTotal 50000이면 무료배송이다", () => {
expect(isEligibleForFreeShipping(50000, "일반")).toBe(true)
})
test("cartTotal 10000이면 무료배송이 아니다", () => {
expect(isEligibleForFreeShipping(10000, "일반")).toBe(false)
})두 테스트 모두 실행하면 브랜치 커버리지는 100%가 된다. 하지만
모듈 3에서 배운 경계값(정확히 49999, 50001)은 여전히 검증
안 됐다. 커버리지 도구는 "이 줄/분기가 실행됐는가"만 셀 뿐, "올바른
경계값으로 실행됐는가"까지는 판단하지 못한다.
커버리지는 "무엇을 안 했는지"를 알려줄 뿐이다
커버리지 리포트의 진짜 가치는 "우리 테스트가 완벽하다"를 증명하는 데 있지 않다. **"이 부분은 테스트가 아예 한 번도 실행해 본 적이 없다"**는, 완전히 놓친 영역을 찾아내는 데 있다. 커버리지가 낮은 영역을 우선순위로 삼아 테스트를 보강하는 것은 유용하지만, 커버리지 숫자 자체를 목표로 삼으면 모듈 6(레슨 5)에서 배운 "지표를 목표로 삼을 때의 왜곡"이 그대로 재현된다.
실무에서 왜 필요한가
"커버리지 90% 달성"이 팀 목표가 되면, 개발자가 의미 없는 테스트로 숫자만 채우는 경우가 실무에서 흔하다(예: 함수를 호출만 하고 아무것도 검증(Assert)하지 않는 테스트). QA가 커버리지 리포트를 읽을 때 "라인은 채워졌지만 실제로 무엇을 검증하는지"까지 확인할 수 있으면, 이런 숫자 채우기용 테스트를 가려낼 수 있다.
실습 과제
과제 1 — 라인 vs 브랜치 커버리지 계산하기 (10분)
이 레슨의 isEligibleForFreeShipping 함수에 대해, "VIP 회원"
테스트 하나만 있을 때와 "장바구니 50,000원 이상" 테스트까지
추가했을 때 각각 브랜치 커버리지가 몇 %인지 어림잡아 계산한다
(함수 안에 조건 분기가 총 몇 개인지부터 센다).
과제 2 — 의미 없는 테스트 찾기 (10분)
다음 테스트가 라인 커버리지는 올리지만 실질적으로 아무것도 검증하지 않는 이유를 설명한다.
test("할인 계산 함수가 실행된다", () => {
calculateDiscount(10000, 0.1)
})자가 체크리스트
- 라인 커버리지와 브랜치 커버리지의 차이를 예시로 설명할 수 있다
- 브랜치 커버리지 100%여도 경계값 결함을 놓칠 수 있는 이유를 설명할 수 있다
- 커버리지 숫자를 목표로 삼을 때 생기는 왜곡을 설명할 수 있다
- 어서션 없이 함수만 호출하는 "가짜 테스트"를 알아볼 수 있다
흔한 실수
- 라인 커버리지 100%를 보고 "이 코드는 완벽히 검증됐다"고 안심한다. 조건의 한쪽 분기만 탔을 수도 있다.
- 커버리지를 팀의 성과 지표로 강제해서, 의미 없는 테스트로 숫자만 채우는 부작용을 만든다. 커버리지는 "놓친 부분을 찾는 도구"로 써야지, 그 자체가 목표가 되면 안 된다.
- 브랜치 커버리지 100%면 경계값까지 다 검증됐다고 착각한다. 커버리지 도구는 "실행됐는가"만 볼 뿐, "올바른 경계값으로 실행됐는가"는 별도로 모듈 3의 설계 기법으로 확인해야 한다.
참고 자료
- ISTQB Glossary — Code Coverage, Branch Coverage — 커버리지 종류의 공식 정의
- Martin Fowler — Test Coverage — 커버리지 지표의 한계를 다룬 아티클