우당탕탕 JaCoCo 도입기

Q_Hyun·2023년 6월 15일
1
post-thumbnail

JaCoCo란?

JaCoCo should provide the standard technology for code coverage analysis in Java VM based environments. The focus is providing a lightweight, flexible and well documented library for integration with various build and development tools. - JaCoCo Docs

JaCoCo는 Java VM 기반 환경에서 코드 범위 분석을 위한 표준 기술을 제공해야 합니다. 다양한 빌드 및 개발 도구와 통합할 수 있도록 가볍고 유연하며 문서화된 라이브러리를 제공하는 것에 중점을 둡니다. - 파파고

JaCoCo는 Java Code Coverage Library의 줄인 말로, 코드 커버리지에 대한 분석을 해주는 도구이다.

코드 커버리지에 대한 분석 리포트를 만들어주고, 커버리지에 대한 룰을 설정할 수 있어서, 관련 룰을 지키지 못하면 커버리지 검증에서 실패하게 된다.

이 기능들을 테스트가 작동될 때 함께 작동되도록 설정해서 빌드, 혹은 테스트 시에 룰을 지키지 못한다면 실패하여 테스트 코드를 더욱 꼼꼼하게 작성할 수 있도록 도움을 준다.


적용

build.gradle

아래 코드는 프로젝트에서 사용되고 있는 build.gradle의 JaCoCo와 관련된 전체 설정이다.

우아한 형제들 블로그에서 사용하는 코드를 가져와 사용했고, 필요한 부분은 수정하여 사용했다.

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.1.0'
    id 'io.spring.dependency-management' version '1.1.0'
    id 'org.asciidoctor.jvm.convert' version '3.3.2'
    id 'jacoco'  // <- 추가
}

...

tasks.named('test') {
    outputs.dir snippetsDir
    useJUnitPlatform()
    finalizedBy 'jacocoTestReport'  <- 추가 
}

// Jacoco 관련 설정
jacoco {
    toolVersion = '0.8.8'
}
// report 생성
jacocoTestReport {
    dependsOn test
    reports {
        html.enabled true
        xml.enabled false
        csv.enabled false
    }
    afterEvaluate { // report에서 제외함. 안보여주기만 하는 것.
        classDirectories.setFrom(files(classDirectories.files.collect {
            fileTree(dir: it, exclude: [
                    'com/kk/jjiiim/JjiiimApplication.class'
            ])
        }))
    }

    finalizedBy 'jacocoTestCoverageVerification'
}

// violationRules에 따라서 커버리지를 만족하는지 검증
jacocoTestCoverageVerification {
    afterEvaluate { // verify에서 제외함. 퍼센티지에서 제거함.
        classDirectories.setFrom(files(classDirectories.files.collect {
            fileTree(dir: it, exclude: [
            'com/kk/jjiiim/JjiiimApplication.class'
            ])
        }))
    }
    violationRules {
        rule {
            // 룰을 간단히 켜고 끌 수 있다.
            enabled = true
            // 룰을 체크할 단위는 클래스 단위
            element = 'CLASS'
            // 브랜치 커버리지를 최소한 90% 만족시켜야 한다.
            limit {
                counter = 'BRANCH'
                value = 'COVEREDRATIO'
                minimum = 0.90
            }
            // 라인 커버리지를 최소한 80% 만족시켜야 한다.
            limit {
                counter = 'LINE'
                value = 'COVEREDRATIO'
                minimum = 0.80
            }
            // 빈 줄을 제외한 코드의 라인수를 최대 200라인으로 제한한다.
            limit {
                counter = 'LINE'
                value = 'TOTALCOUNT'
                maximum = 200
            }
        }
    }
}

task testCoverage(type: Test) { // testCoverage라는 task가 생성됨.
    group 'verification'
    description 'Runs the unit tests with coverage'

		// test -> jacocoTestReport -> jacocoTestCoverageVerification 순으로 작동한다.
    dependsOn(':test',
            ':jacocoTestReport',
            ':jacocoTestCoverageVerification')

    tasks['jacocoTestReport'].mustRunAfter(tasks['test'])
    tasks['jacocoTestCoverageVerification'].mustRunAfter(tasks['jacocoTestReport'])
}

⚠️이슈 1. Java 버전 호환 이슈 - Java17은 0.8.8 부터 사용

배경

Jacoco를 설정하기 위해서 관련 자료들을 참고해서 설정을 한 후, 테스트를 돌려봤는데, 성공했다고 나오기는 하는데, 이상한 에러 로그들이 발생했다.

아래 로그를 살펴보니 jacoco~~~ 이러는 것 같아서 해당 내용에 대해서 검색을 해보았다.

java.lang.instrument.IllegalClassFormatException: Error while instrumenting org/springframework/test/annotation/DirtiesContext$MethodMode.
	at org.jacoco.agent.rt.internal_43f5073.CoverageTransformer.transform(CoverageTransformer.java:94)

 .... (생략)

[StackOverFlow] Jacoco java.lang.instrument.IllegalClassFormatException: Error while instrumenting Class

아래와 같은 답변을 보았고, build.gradle에서 버전을 0.8.8로 바꾸니 성공했다.

Jacoco Docs

0.8.8 버전부터 Java 17과 18을 공식적으로 지원한다고 한다.

0.8.9 버전은 Java 19와 20 역시 지원한다고 하니 고려해서 버전을 설정해야겠다.

Release 0.8.8 (2022/04/05)
New Features
JaCoCo now officially supports Java 17 and 18 (GitHub #1282, #1198).
Experimental support for Java 19 class files (GitHub #1264).
Part of bytecode generated by the Java compilers for assert statement is filtered out during generation of report (GitHub #1196).
Branch added by the Kotlin compiler version 1.6.0 and above for "unsafe" cast operator is filtered out during generation of report (GitHub #1266).
Improved support for multiple JaCoCo runtimes in the same VM (GitHub #1057).
Fixed bugs
Fixed NullPointerException during filtering (GitHub #1189).
Fix range for debug symbols of method parameters (GitHub #1246).
Non-functional Changes
JaCoCo now depends on ASM 9.2 (GitHub #1206).
Messages of exceptions occurring during analysis or instrumentation now include JaCoCo version (GitHub #1217).

⚠️이슈 2. 특정 파일 excludes 하기

배경

아래 사진은 Jacoco에 관련한 설정을 마친 후, 테스트 코드를 작성해보고 JaCoCo를 통해 획득한 리포트이다.

Jacoco에서 커버리지를 80%로 설정했으나, SpringBootApplication 클래스의 커버리지가 33%밖에 나오지 않아서 검증에 실패했고, 빌드를 못하게 되었다.

이후, 테스트하기 어려운 상황이 생길 수 있을 것 같은데 미리 이 클래스를 점검에서 빼버리고자 했다.

찾아낸 방법은 아래 구문을 jacocoTestReport , jacocoTestCoverageVerification 에 추가하는 것이다.

fileTree 아래 경로를 작성하면 task를 진행할 때 저 부분을 제외하고 진행하는 것 같다.

jacocoTestReport 에 작성할 경우 report에 해당 파일 부분이 나타나지 않고, jacocoTestCoverageVerification 에 작성할 경우 해당 파일에 대한 coverage를 점검하지 않는다.

afterEvaluate { 
        classDirectories.setFrom(files(classDirectories.files.collect {
            fileTree(dir: it, exclude: [
            'com/kk/jjiiim/JjiiimApplication.class'
            ])
        }))
    }

결과


참고 자료

코드 분석 도구 적용기 - 2편, JaCoCo 적용하기
Gradle 프로젝트에 JaCoCo 설정하기 | 우아한형제들 기술블로그
Exclusions from Jacoco Report | Baeldung
Jacoco 테스트 커버리지 excludes 안되는 문제
Jacoco java.lang.instrument.IllegalClassFormatException: Error while instrumenting Class
Jacoco & Gradle - How to verify coverage with exclusions

0개의 댓글