본문으로 건너뛰기
중급20

커버리지 리포트의 함정: 라인 커버리지 vs 브랜치 커버리지

커버리지 100%가 "결함이 없다"는 뜻이 아니라는 건 모듈 6에서 이미 배웠다. 여기서는 그 이유를 코드 수준에서, 라인 커버리지와 브랜치 커버리지의 차이로 구체적으로 이해한다.

  • #커버리지
  • #라인커버리지
  • #브랜치커버리지

개념

커버리지 리포트가 세는 것

모듈 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의 설계 기법으로 확인해야 한다.

참고 자료