사전 테스트 커버리지
개념

테스트 커버리지

gabury1고친 사람 github-actions[bot]

테스트 커버리지는 테스트가 코드의 어디까지 닿았는지 알려 줍니다. 테스트를 돌리는 동안 실행된 코드가 전체에서 얼마인지를 비율로 냅니다. 한 번도 실행되지 않은 코드가 드러나므로 테스트를 더 써야 할 곳을 찾을 수 있습니다. 실행된 코드가 제대로 검사됐는지까지는 알려 주지 않습니다.

쉽고 빠른 이해

무슨 일을 하나 — 테스트를 돌리는 동안 코드의 어느 줄이 실행됐는지 세어 비율로 알려 줍니다. 코드가 100줄인데 테스트가 80줄을 지나갔다면 나머지 20줄은 테스트가 한 번도 안 닿은 곳입니다.

왜 하나 — 테스트가 전부 통과해도 테스트가 안 닿은 코드는 깨져도 아무도 모릅니다. 커버리지 보고서는 그런 코드를 줄 단위로 짚어 줍니다.

어떻게 도나

  1. 도구가 코드 곳곳에 「여기를 지나갔다」를 적는 장치를 끼워 넣습니다
  2. 평소처럼 테스트를 돌립니다
  3. 적힌 기록을 모아 실행된 줄과 안 된 줄을 보여 줍니다

대가 — 실행만 하고 결과를 확인하지 않는 테스트도 비율을 올립니다. 그래서 숫자가 높아도 테스트가 허술할 수 있습니다. 숫자를 목표로 삼으면 이런 테스트가 늘어납니다.

상세

이 절은 할인 금액을 계산하는 함수 하나와 그 함수를 부르는 테스트를 가지고 테스트 커버리지를 봅니다. 무엇을 세는지, 어떻게 세는지, 숫자가 무엇을 말해 주지 않는지를 차례로 봅니다.

건물 순찰을 도는 경비원이 들른 방마다 순찰표에 도장을 찍습니다. 하루가 끝나면 도장이 없는 방이 한눈에 보입니다. 그 방은 오늘 아무도 문을 열어 보지 않은 방입니다.

테스트 커버리지는 테스트를 돌리는 동안 한 번이라도 실행된 코드가 전체 코드에서 차지하는 비율입니다. 코드가 100줄인데 테스트가 그중 80줄을 지나갔다면 커버리지는 100줄 중 80줄, 곧 5분의 4입니다. 보고서는 이 비율을 대개 백분율로 적습니다. 나머지 20줄은 테스트가 한 번도 건드리지 않은 코드입니다.

이 숫자가 필요한 까닭은 테스트 결과가 테스트를 쓴 곳까지만 말해 주기 때문입니다. 여러 테스트를 묶어 한꺼번에 돌리는 테스트 스위트가 전부 통과해도, 테스트가 안 닿은 코드는 깨져 있을 수 있습니다. 커버리지는 그 빈 곳을 찾아 줍니다. 안 지나간 줄을 보면 어디에 테스트를 더 써야 할지 보입니다.

코드를 기준으로 재는 이 비율을 따로 코드 커버리지라고도 부릅니다. 실무에서 테스트 커버리지라고 하면 대개 코드 커버리지를 가리킵니다. 요구사항을 기준으로 재는 쓰임도 있습니다. 그 쓰임은 이 절의 마지막 소절에서 봅니다.

실행된 코드를 세는 방법

이 소절은 커버리지 도구가 「실행됐다」를 어떻게 아는지를 봅니다.

도구는 테스트를 돌리기 전에 코드 곳곳에 기록 장치를 끼워 넣습니다. 줄마다, 갈림길마다 「여기를 지나갔다」를 적는 작은 코드입니다. 동작을 재려고 프로그램에 이런 코드를 덧붙이는 일을 계측이라고 합니다. 원래 코드가 하는 일은 바뀌지 않습니다.

그다음 평소처럼 테스트를 돌립니다. 테스트가 코드를 지나갈 때마다 기록 장치가 표시를 남깁니다. 앞의 경비원이 찍던 도장이 이 표시입니다. 할인 함수에 기록 장치를 끼웠다면, 테스트가 지나간 줄마다 표시가 하나씩 붙습니다.

테스트가 끝나면 도구가 표시를 모아 보고서를 만듭니다. 보고서는 파일마다 비율을 보여 줍니다. 실행된 줄과 안 된 줄은 대개 다른 색으로 칠합니다.

계측된 코드는 기록하는 일을 더 하므로 느려집니다. 그래서 커버리지를 재는 테스트 실행은 대개 평소보다 오래 걸립니다.

커버리지는 코드를 돌려야 나오는 숫자입니다. 프로그램을 실행하면서 살피는 일을 동적 분석이라고 합니다. 커버리지 측정은 동적 분석의 하나입니다.

그 반대편에는 정적 분석이 있습니다. 코드를 실행하지 않고 읽기만 해서 문제를 찾는 일입니다.

세는 단위에 따라 달라지는 숫자

이 소절은 같은 테스트라도 무엇을 세느냐에 따라 커버리지가 달라지는 것을 봅니다. 아래 할인 함수와 테스트 하나를 씁니다.

할인 함수는 2만 원 이상이면 2천 원을 깎습니다. 2만 원 미만이면 깎지 않습니다. 오른쪽 주석은 테스트를 한 번 돌렸을 때 그 줄이 실행됐는지를 적은 것입니다.

Java
int discount(int price) {
    int d = 0;              // 지나감
    if (price >= 20000) {   // 지나감
        d = 2000;           // 지나감
    }
    return d;               // 지나감
}

테스트는 3만 원을 넣어 봅니다. 돌려받는 값은 오른쪽 주석에 적었습니다.

Java
discount(30000);   // 2000

프로그램에서 실행되는 명령 하나하나를 구문이라고 합니다. 이 함수에서는 주석이 달린 네 줄이 구문입니다. 테스트 하나가 넷을 모두 지나갔습니다. 실행된 구문의 비율을 구문 커버리지라고 하니, 이 테스트의 구문 커버리지는 넷 중 넷으로 꽉 찹니다.

그런데 2만 원 미만이 들어오는 경우는 한 번도 돌려 보지 않았습니다. if 는 조건이 참일 때와 거짓일 때 두 갈래로 나뉩니다. 이 갈래 하나하나를 분기라고 합니다.

지나간 분기의 비율이 분기 커버리지입니다. 이 테스트는 두 분기 중 참 쪽만 지났으므로 분기 커버리지는 둘 중 하나, 곧 절반입니다.

flowchart TD
    A["discount(30000) 을 부른다"] --> B{"price >= 20000"}
    B -->|"참 · 지나감"| C["d = 2000"]
    C --> R["return d"]
    B -.->|"거짓 · 안 지나감"| R

그림에서 점선이 테스트가 한 번도 안 간 길입니다. 거짓 쪽 길에는 자기 몫의 구문이 하나도 없습니다. d 가 0 인 채로 곧장 return d 로 갑니다. 그래서 구문만 세면 이 길이 빠진 것이 안 보입니다.

분기를 모두 지나면 구문도 모두 지나게 됩니다. 반대는 성립하지 않습니다. 앞의 함수가 그 반례입니다. 그래서 분기 커버리지는 구문 커버리지보다 엄격한 기준입니다.

세는 단위를 더 잘게 잡는 기준도 있습니다. 조건식이 a > 0 && b > 0 처럼 && 나 || 로 이어져 있으면, a > 0 과 b > 0 을 따로 떼어 작은 조건 하나로 봅니다. 작은 조건마다 참과 거짓을 모두 겪었는지 세는 것이 조건 커버리지입니다.

할인 함수의 조건은 price >= 20000 하나뿐이라 작은 조건도 하나입니다. 이 조건의 참과 거짓 둘을 셉니다. 앞의 테스트는 참만 겪었습니다.

경로는 함수 처음부터 끝까지 가는 길입니다. 할인 함수에서는 그림의 두 길이 경로입니다. 이 경로 가운데 지나간 것의 비율이 경로 커버리지입니다.

아래 표는 흔히 쓰는 기준 넷에 앞의 테스트 하나로 잰 값을 붙였습니다.

기준 세는 것 앞 테스트의 값
구문 커버리지 실행된 구문 4개 중 4개 · 전부
분기 커버리지 지나간 분기 2개 중 1개 · 절반
조건 커버리지 작은 조건마다 겪은 참과 거짓 2개 중 1개 · 절반
경로 커버리지 함수 처음부터 끝까지 가는 길 2개 중 1개 · 절반

표에서 볼 것은 셋째 칸입니다. 같은 테스트 하나인데 기준에 따라 꽉 차기도 하고 절반에 그치기도 합니다. 그래서 커버리지 숫자는 어느 기준으로 잰 것인지를 함께 밝혀야 뜻이 섭니다.

경로 커버리지는 다 채우기가 가장 어렵습니다. 갈림길이 하나 늘 때마다 경로 수가 많게는 두 배로 늡니다. 갈림길 열 개가 차례로 놓이면 경로는 많게는 1,024 가지입니다. 반복문이 있으면 도는 횟수마다 다른 경로가 생겨 끝이 없습니다.

그래서 실무에서는 대개 구문 커버리지와 분기 커버리지를 봅니다. 경로 커버리지를 끝까지 채우는 일은 갈림길이 몇 개 안 되는 함수에서나 해낼 수 있습니다.

지나간 것과 확인한 것의 차이

이 소절은 커버리지가 꽉 차도 테스트가 허술할 수 있는 까닭을 봅니다. 앞의 할인 함수를 다시 씁니다.

테스트는 코드를 부르는 것으로 끝나지 않습니다. 돌려받은 값이 기대한 값과 같은지 확인해야 합니다. 이 확인 문장을 단언이라고 합니다. 아래 첫째 테스트는 단언이 있습니다.

Java
int got = discount(30000);  // 2000
assertEquals(2000, got);    // 통과

둘째 테스트는 함수를 부르기만 하고 돌려받은 값을 보지 않습니다.

Java
discount(30000);            // 2000

두 테스트의 커버리지는 같습니다. 둘 다 할인 함수의 구문 넷을 전부 지나가므로 구문 커버리지가 꽉 찹니다. 그런데 둘째 테스트는 할인 함수가 3천 원을 돌려줘도 통과합니다. 값을 확인하지 않기 때문입니다.

커버리지는 코드가 실행됐는지를 셉니다. 결과가 맞았는지는 보지 않습니다. 그래서 커버리지가 높다는 것은 「테스트가 이 코드를 지나갔다」까지만 말해 줍니다. 「이 코드가 맞다」는 말해 주지 않습니다.

없는 코드도 못 봅니다. 음수 가격이 들어오면 막아야 하는데 그 검사를 아무도 안 썼다고 해 봅시다. 커버리지는 있는 줄만 세므로, 빠진 검사는 보고서 어디에도 나타나지 않습니다.

테스트가 결함을 잡아내는 힘을 따로 재는 방법으로 뮤테이션 테스트가 있습니다. 코드를 일부러 조금 바꾼 뒤 테스트가 실패하는지 봅니다. price >= 20000 을 price > 20000 으로 바꿨는데도 테스트가 통과한다면, 그 테스트는 2만 원 경계를 확인하지 않는 것입니다.

커버리지 목표치와 그 대가

이 소절은 팀이 커버리지 숫자를 합격선으로 쓸 때 생기는 일을 봅니다.

코드를 올릴 때마다 서버가 빌드하고 테스트를 돌려 주는 방식을 지속적 통합이라고 합니다. 이때 커버리지도 함께 재서 하한을 걸어 두는 팀이 많습니다. 하한을 정해 두었다면, 커버리지를 그 아래로 떨어뜨리는 변경은 받지 않습니다.

숫자가 목표가 되면 숫자를 올리는 일이 쉬운 쪽으로 흐릅니다. 단언 없이 함수를 부르기만 하는 테스트는 쓰기 쉽고 커버리지를 빠르게 올립니다. 그렇게 오른 숫자는 테스트가 튼튼해졌다는 뜻이 아닙니다.

재는 값을 목표로 삼으면 그 값은 더는 본래 재려던 것을 가리키지 못합니다. 이 관찰을 굿하트의 법칙이라고 부릅니다.

빈 곳 없이 다 채우는 데에도 대가가 있습니다. 마지막으로 남는 몇 줄은 대개 테스트로 일으키기 어려운 코드입니다. 디스크가 가득 찼을 때 도는 예외 처리가 그런 코드입니다. 이런 코드를 지나가게 하려면 테스트 준비가 크게 늘어납니다.

커버리지 보고서의 다른 쓰임은 빈 곳을 찾는 지도입니다. 안 지나간 줄을 하나씩 보며 테스트가 없는 까닭을 묻습니다. 일부러 안 쓴 것인지, 잊은 것인지가 거기서 갈립니다.

요구사항을 기준으로 재는 커버리지

이 소절은 같은 이름의 다른 쓰임을 봅니다. 기준이 코드가 아니라 요구사항입니다.

품질 보증을 맡는 쪽에서는 테스트 커버리지를 요구사항 기준으로도 셉니다. 요구사항 가운데 테스트가 하나라도 붙은 것이 얼마인지를 비율로 냅니다. 기능 요구사항이 스무 개인데 열여섯 개에 테스트가 있으면 요구사항 기준 커버리지는 5분의 4입니다. 이 비율을 따로 요구사항 커버리지라고도 부릅니다.

이 쓰임은 코드를 돌리지 않고도 잽니다. 요구사항과 테스트를 짝지은 표만 있으면 됩니다. 그런 표를 추적성 매트릭스라고 부릅니다.

두 쓰임은 빈 곳을 묻는 방향이 다릅니다. 코드 기준은 「이 줄을 지나간 테스트가 있나」를 묻습니다. 요구사항 기준은 「이 기능을 확인하는 테스트가 있나」를 묻습니다. 앞 소절들의 이야기는 전부 코드 기준입니다.

관련 항목

테스트 커버리지를 세는 기준

코드 커버리지 · 구문 커버리지 · 라인 커버리지 · 분기 커버리지 · 조건 커버리지 · 경로 커버리지 · 함수 커버리지

테스트 커버리지를 재는 도구

계측 · JaCoCo · coverage.py · Istanbul · gcov · 커버리지 보고서

테스트 커버리지가 재는 테스트 종류

단위 테스트 · 통합 테스트 · 회귀 테스트 · 테스트 스위트 · 테스트 케이스

테스트 커버리지를 보완하는 검사 방법

뮤테이션 테스트 · 단언 · 속성 기반 테스트 · 퍼징 · 코드 리뷰

테스트 커버리지 수치를 합격선으로 쓰는 개발 단계

지속적 통합 · 배포 파이프라인 · 품질 게이트 · 풀 리퀘스트

테스트 커버리지와 맞세워지는 분석 방식

정적 분석 · 동적 분석 · 프로파일링

테스트 커버리지와 나란히 쓰는 품질 지표와 해석 원칙

소프트웨어 지표 · 순환 복잡도 · 결함 밀도 · 허영 지표 · 굿하트의 법칙

요구사항 쪽에서 재는 커버리지 지표

요구사항 커버리지 · 추적성 매트릭스 · 테스트 커버리지 항목 · 요구사항 · 인수 테스트

테스트 커버리지가 속하는 상위 분류

QA와 테스트 · 소프트웨어 테스트 · 품질 보증 · 소프트웨어 품질

다른 이름: test coverage