jacoco로 테스트 커버리지 관리하기

jacoco로 테스트 커버리지 관리하기

모든 테스트 결과시각화하여 테스트 커버리지를 세밀하게 관리해봅시다


📕 참고



🤔 jacoco


jacoco는 그냥 아주 간단하게 생각하면 된다.

테스트 코드의 결과를 문서로 시각화해주는 툴이다.

부가적으로 테스트 결과에 대한 검증도 가능하다.

gradle을 사용중이라면 이미 jacoco 플러그인이 기본적으로 내장돼있기 때문에 사용도 매우 간단하다.


// file: 'build.gradle'
plugins {
    id 'jacoco'
}


이렇게 Plugin DSL에 딱 한줄만 추가해주면 적용이 끝난다.

정말 이게 끝인지 의문스러울 수 있다.

정말 이게 끝이다.


📕 jacoco task


플러그인을 적용해주면 두개의 jacoco 태스크가 새로 생성된다.


$ gradle tasks

> Task :tasks
------------------------------------------------------------
Tasks runnable from root project 'spring-rest-docs'
------------------------------------------------------------

...


Verification tasks
------------------
check - Runs all checks.
jacocoTestCoverageVerification - Verifies code coverage metrics based on specified rules for the test task.
jacocoTestReport - Generates code coverage report for the test task.
test - Runs the unit tests.


...

To see all tasks and more detail, run gradle tasks --all

To see more detail about a task, run gradle help --task <task>

BUILD SUCCESSFUL in 773ms
1 actionable task: 1 executed


jacoco 태스크는 순서가 정해져있다.

test -> jacocoTestReport -> jacocoTestCoverageVerification 순으로 실행되는게 가장 바람직하다.

각 태스크에 대해 알아보자.


📜 jacocoTestReport


테스트 결과에 대한 분석을 문서로 작성해주는 태스크다.

jacoco를 통해 다음 예시와 같은 문서가 만들어진다.


📜 jacoco test report


image


생성된 문서를 보면 라인에 초록색, 빨간색, 노란색이 칠해져 있다.

이것은 라인 커버라고 불리며 의미는 다음과 같다.


  • 초록: 테스트 코드에서 호출되었으며 충분히 검증되었음
  • 빨강: 테스트 코드로 검증되지 않았음
  • 노랑: 테스트 코드에서 라인의 일부만 호출 및 검증되었음 (주로 조건문이나 메서드 체이닝으로 인해 발생)


jacoco 태스크는 일단 별도의 수정 없이 그냥 사용해도 무방하지만 필자는 jacoco 태스크를 입맛대로 사용하기 위해 약간의 커스터마이징을 시도했다.


// file: 'build.gradle'
ext {
    set('staticsDir', file('src/main/resources/static')) // 문서가 저장될 위치변수

    // jacoco 필터링 목록
    excludeFilter = [ 
            '**/dto/**', // 단순 입출력용 클래스
            '**/*Application.*', // 메인 클래스
            '**/Q*.class', // Querydsl 클래스
            '**/test/**', // 테스트 클래스
    ]
}

jacocoTestReport {
    reports {
        html.enabled true // 테스트 보고서를 html로 생성할 것
        xml.enabled false // 테스트 보고서를 xml로 생성하지 않을 것
        csv.enabled false // 테스트 보고서를 csv로 생성하지 않을 것

        html.destination file("$staticsDir/coverage") // 테스트 보고서를 생성할 위치
    }

    afterEvaluate {
        classDirectories.setFrom(files(classDirectories.files.collect {
            fileTree(dir: it, exclude: excludeFilter) // 문서에 출력하고 싶지 않은 파일들에 대한 목록
        }))
    }
}


📜 jacocoTestCoverageVerification


테스트 커버리지에 대한 검증 태스크다.

이 태스크에서 정말 많은것을 할 수 있다.

jacoco를 사용하면서 유난히 손이 좀 가는 태스크이긴 한 것 같다.

역시 빌드 스크립트를 보는게 빠르다.


// file: 'build.gradle'
ext {
    set('staticsDir', file('src/main/resources/static')) // 문서가 저장될 위치변수

    // jacoco 필터링 목록
    excludeFilter = [ 
            '**/dto/**', // 단순 입출력용 클래스
            '**/*Application.*', // 메인 클래스
            '**/Q*.class', // Querydsl 클래스
            '**/test/**', // 테스트 클래스
    ]
}

jacocoTestCoverageVerification {
    // 검증 룰 선언
    violationRules { 
        rule {
            enabled = true // 해당 룰을 사용할 것

			excludes = excludeFilter // 테스트 결과를 검증하지 않을 파일들에 대한 목록

            element = 'CLASS' // 클래스 파일을 대상으로
            
            limit { // 제한한다
                counter = 'LINE' // 라인에 대해서
                value = 'COVEREDRATIO' // 라인 커버리지가
                minimum = 0.50 // 최소 50%가 넘어야 한다
            }

            limit { // 제한한다
                counter = 'LINE' // 라인에 대해서
                value = 'TOTALCOUNT' // 라인 수(LOC)
                maximum = 250 // LOC가 250줄이 넘으면 안된다
            }
        }
    }
}


이런식으로 클래스, 패키지, 라인등을 타겟으로 시스템적 제약을 걸 수 있다.

이렇게 테스트 결과를 다시 검증하며 이 이 위반되었을 경우 빌드를 실패시키고, 왜 실패했는지를 알려준다.


📜 jacoco(custom task)


이정도만 설정하게 되면 개발자가 매번 태스크를 수동으로 실행시켜줘야하는 불편함이 있기 때문에, build 태스크가 실행되면 모든 작업이 자동화되게 만들 것이다.


// file: 'build.gradle'
task jacoco(type: Test) { // jacoco라는 이름의 커스텀 태스크
    group 'verification'
    description 'Runs the unit tests and verify coverage using jacoco' // 태스크가 실행되면 출력될 메시지

    // 이 태스크가 실행되면 다음의 두 태스크가 먼저 실행되게 한다
    dependsOn(
            ':jacocoTestReport',
            ':jacocoTestCoverageVerification'
    )

    // jacocoTestCoverageVerification는 반드시 jacocoTestReport가 실행된 후에 실행될 것
    tasks['jacocoTestCoverageVerification'].mustRunAfter(tasks['jacocoTestReport'])
}

bootJar {
    dependsOn(':jacoco') // boorJar 태스크가 실행되기 전(패키징단계)에 jacoco 태스크가 실행되도록 한다
}


이렇게 설정하고 gradle clean build를 실행하면…


$ gradle clean build

Starting Gradle Daemon...
Gradle Daemon started in 2 s 336 ms

> Task :compileJava
> Task :processResources
> Task :classes
> Task :bootJarMainClassName
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses

> Task :test // 테스트 실행 
> Task :jacocoTestReport // 테스트 리포트 작성 
> Task :jacocoTestCoverageVerification // 테스트 리포트를 토대로 검증
> Task :jacoco // jacoco 태스크 실행(앞의 두 태스크를 유발하는 것 외에 아무 기능 없음)
> Task :check
> Task :bootJar // 패키징

> Task :jar
> Task :assemble
> Task :build

BUILD SUCCESSFUL in 19s
11 actionable tasks: 11 executed
오후 1:06:09: Task execution finished 'build'.



© 2022. All rights reserved.