Java & Spring

[JaCoCo] 코드 커버리지 측정

ju_young 2024. 7. 10. 01:24
728x90

테스트 커버리지

  • 테스트 범위를 측정하는 테트 품질 측정 기준이며, 테스트의 정확성과 신뢰성을 향상시키는 역할
  • 테스트 케이스의 부족한 부분을 파악하고 추가적인 테스트 케이스를 개발할 수 있음

테스트 커버리지의 종류

1. 구문 커버리지 (Statement Coverage)

  • 모든 구문에 대해 한 번 이상 수행하는 테스트 커버리지
  • 테스트 케이스 집합에 의해 실행된 문장의 수 / (전체 실행 가능한 프로그램 문장의 수) * 100%
if (x > 0) {
    y = x * 2;
} else {
    y = x / 2;
}
  • x의 값을 3으로 설정한 경우 -> 첫 번째 구문이 True가 되어 두 번째 구문이 실행
  • x의 값을 -2 또는 0으로 설정한 경우 -> 첫 번째 구문이 False가 되어 네 번째 구문이 실행

이처럼 모든 구문을 한 번씩 실행하는 것이 구문 커버리지를 충족하는 것이다.

2. 결정 커버리지 (Decision Coverage)

  • 결정 포인트 내의 전체 조건식이 적어도 한 번은 참(T)과 거짓(F)의 결과를 수행하는 테스트 커버리지
  • 분기 커버리지(Branch Coverage)라고도 함
  • (테스트 케이스 집합에 의해 실행된 결정의 결과 수 / 전체 프로그램 결과 수) * 100%
if (x > 1 || y > 3) {
    z = z * x
} elif (x < 2 && y > 1) {
    z = z + 1
}
테스트 케이스 x > 1 || y > 3 (A 분기문) x < 2 && y > 1 (B 분기문)
x=1.5, y=2, z=2 T T
x=3, y=2, z=1 T F
x=1, y=3, z=3 F F

첫 번째와 두 번째 테스트 케이스만 수행하면 B 분기만 결정 커버리지를 만족하고, A 분기문의 결정 커버리지는 만족하지 못한다. 따라서 세 번째 테스트 케이스까지 모두 수행해야지 결정 커버리지를 100% 만족하게 된다.

3. 조건 커버리지 (Condition Coverage)

  • 결정 포인트 내의 개별 조건식이 적어도 한 번은 참(T)과 거짓(F)의 결과를 수행하는 테스트 커버리지
  • (테스트 케이스 집합에 의해 실행된 조건의 결과 수 / 전체 프로그램 조건의 결과 수) * 100%
if (x > 1 || y > 3) {
    z = z * x
} elif (x < 2 && y > 1) {
    z = z + 1
}
테스트 케이스 x > 1 (A 분기문) y > 3 (A 분기문) x < 2 (B 분기문) y > 1 (B 분기문)
x=3, y=0.5, z=2 T F F F
x=1, y=3, z=3 F F T T
x=0.5, y=4, z=1 F T T T

첫 번째, 두 번째 테스트 케이스까지 수행할 경우 y > 3이 True가 되는 경우를 테스트하지 못한다. 조건 커버리지의 경우 개별 조건식이 적어도 한 번은 참(T)과 거짓(F)의 결과가 되는 테스트 케이스를 가져야하기 때문에, 세 번째 테스트 케이스까지 수행해야지 조건 커버리지를 100% 만족하게 된다.


커버리지 측정

내가 작성한 테스트 코드의 커버리지가 몇 %인지 확인하기 위해서 JaCoCo 플러그인을 사용해볼 수 있다.

Configuration

코드 커버리지 계산을 원하는 프로젝트에 다음과 같이 JaCoCo 플러그인을 적용해준다.

plugins {
    id 'jacoco'
}

 

그리고 버전과 report가 생성될 directory를 지정해준다. directory를 따로 지정해주지 않으면 layout.buildDirectory.dir("reports/jacoco")으로 설정된다.

jacoco {
    toolVersion = "0.8.12"
    reportsDirectory = layout.buildDirectory.dir('customJacocoReportDir')
}

report는 XML, CSV, HTML 형식으로 생성할 수 있고 아래처럼 어떤 형식으로 생성할 것인지, 어떤 directory에 생성할 것인지 설정할 수 있다.

jacocoTestReport {
    reports {
        xml.required = false
        csv.required = false
        html.outputLocation = layout.buildDirectory.dir('jacocoHtml')
    }
}

 

여기까지 설정해주고 빌드를 해주면 jacocoHtmlindex.html이 생성되고 열어보면 다음과 같이 각 패키지별로 커버리지를 확인할 수 있다.

Coverage Counter

위 표에서 Instruction, Branch 등 각 칼럼은 커버리지 metric을 의미한다. JaCoCo 공식 문서에서는 Coverage Counter라고 하는데, 각각 어떤 부분을 측정하는 것을 의미하는지 살펴보자.

  • Instructions
    • 가장 기본적인 커버리지 metric으로 자바 바이트코드의 명령어 단위로 커버리지를 측정
    • 코드가 얼마나 실행되었는지를 나타냄
  • Branches
    • 조건문(if, switch 등)의 각 분기가 테스트되었는지를 측정
  • Lines
    • 코드의 각 라인이 얼마나 실행되었는지를 측정
    • 하나의 라인에 여러 명령어가 있을 수 있기 때문에 이 경우 라인 커버리지와 명령어 커버리지가 다를 수 있음
  • Methods
    • 메소드 단위로 커버리지를 측정
  • Classes
    • 클래스 단위로 커버리지를 측정
  • Cyclomatic Complexity(Cxty)
    • 소프트웨어의 복잡도를 측정하는 metric
    • Cxty 값이 노을수록 더 많은 테스트 케이스가 필요

커버리지 제외

DTO 객체, Exception 등 커버리지를 측정하지 않아도되는 대상을 지정하여 제외시켜줄 수 있다.

jacocoTestReport {  
    reports {  
        xml.required = false  
        csv.required = false  
        html.outputLocation = layout.buildDirectory.dir('jacocoHtml')  
    }  

    afterEvaluate {  
        classDirectories.setFrom(files(classDirectories.files.collect {  
            fileTree(dir: it, exclude: [  
                    "**/config/*",  
                    "**/dto/*",  
                    "**/exception/*",  
                    "**/security/*",  
                    "**/entity/*",  
                    "**/constants/*",  
            ])  
        }))  
    }  
}

jacocoTestCoverageVerification

설정한 규칙에 따라 특정 커버리지를 만족하는지 확인할 수 있다. 만족하지 않는다면 빌드에 실패한다.

  • element: BUNDLE, PACKAGE, CLASS, SOURCEFILE, METHOD
  • counter: INSTRUCTION, LINE, BRANCH, COMPLEXITY, METHOD, CLASS
  • value: TOTALCOUNT, COVEREDCOUNT, MISSEDCOUNT, COVEREDRATIO, MISSEDRATIO
jacocoTestCoverageVerification {  
    violationRules {  
        rule {  
            enabled = true  
            element = 'CLASS'  

            limit {  
                counter = 'INSTRUCTION'  
                value = 'COVEREDRATIO'  
                minimum = 0.8
            }  
        }   
    }
}

test 실행 후 항상 report

테스트가 끝나고 항상 jacoco를 실행시키려면 다음과 같이 설정해준다.

test {  
    finalizedBy jacocoTestReport 
}

jacocoTestReport {  
    dependsOn test  
    ...
}

finalizedBy로 테스트가 실행되고 jacocoTestReport task가 실행되도록 해준 것이다.

Appendix

Cyclomatic Complexity

공식적으로 Cyclomatic Complexity 계산식은 M = E - N + 2가 된다.

  • E: 그래프의 엣지 수 (Edges)를 의미
  • N: 그래프의 노드 수 (Nodes)를 의미

JaCoCo는 E를 branch 수, N을 결정 포인트(desision points)로 동일하게 보며 M = B(branch) - D(decision point) + 1 로 계산한다고 한다.

 

[reference]

728x90