본문 바로가기

카테고리 없음

[끄적끄적] Jacoco multi-module 자동화 적용기

[끄적끄적] Jacoco - multi-module 적용기

multi module project에 jacoco를 적용하는중 겪었던 어려움과 해결방법을 작성한 글 입니다.

우선, 제가 만든 jacoco관련 gradle 파일은 다음과 같습니다. 

 

1. 새롭게 생성되는 모듈 jacoco-aggregtaion 대상으로 등록 자동화

2. 새롭게 생성되는 모듈 test coverage 측정 대상으로 등록 자동화

3. coverage-exclude.luffy (저희 프로젝트 내부에서 사용하는 파일입니다) 에 exclude할 패키지 지정하면 모든 테스트와 reports에서 제외 자동화

https://github.com/depromeet/na-lab-server/blob/main/gradle/jacoco.gradle

 

GitHub - depromeet/na-lab-server

Contribute to depromeet/na-lab-server development by creating an account on GitHub.

github.com

Multi-module에서는 Jacoco coverage reports가 따로 생성된다.

Multi-module 애플리케이션 개발시, Jacoco 플러그인을 적용하기 위해서, root의 build.gradle에서, allProjects 혹은 subProjects로 모든 모듈에 Jacoco 플러그인을 적용하실겁니다.

 

하지만, 이렇게 하면, jacoco의 test code coverage reports가 모듈별로 생성된다는 단점이 있습니다. 특정 모듈의 커버리지 리포트를 보기 위해서, 해당 모듈의 build를 열어봐야 하는거죠.

 

이 방법은 너무 귀찮습니다. 또.. sonarcloud와 같은 lint툴에 test code coverage를 적용하기 위해선, 이 분리된 커버리지 reports를 한곳에 몰아둘 필요가 있습니다.

Jacoco-aggregation으로 한 곳에 몰아두기

분리된 jacoco coverage reports파일을 합치기 위해서, jacoco-aggregation 플러그인을 사용할 수 있습니다.

https://docs.gradle.org/current/userguide/jacoco_report_aggregation_plugin.html

 

The JaCoCo Report Aggregation Plugin

The configuration used to declare all project dependencies having code coverage data to be aggregated.

docs.gradle.org

.gradle 파일에 다음과 같이 plugin을 설정해주면 끝... 입니다.

apply plugin: 'jacoco-report-aggregation'

그리고, 해당 모듈의 dependencies에 커버리지를 합칠 모듈을 지정하면, 위 'jacooc-report-aggregation' 플러그인을 적용한 모듈의 build 아래에 합쳐진 jacoco test coverage reports가 생성됩니다.

특정 모듈을 coverage 측정에서 제외하자.

해결하는데 시간이 오래 걸린 문제는 여기서 발생 했습니다.

 

test에서 특정 클래스는 아래와 같은 방법으로 쉽게 제거가 가능합니다 

excludes에 특정 패키지를 지정해주면 되죠

    jacocoTestCoverageVerification {
        violationRules {

            rule {
                enabled = true
                element = 'CLASS'
                excludes += ['~~패키지']

                limit {
                    counter = 'LINE'
                    value = 'COVEREDRATIO'
                    minimum = 0.70
                }

                limit {
                    counter = 'BRANCH'
                    value = 'COVEREDRATIO'
                    minimum = 0.70
                }

            }

        }
    }

 

그런데, 이렇게 해줘도.. jacoco-aggregation으로 생성되는 test coverage reports와, 각 모듈의 coverage reports에는 여전히, 테스트 커버리지가 남아있는걸 볼 수 있습니다. (심지어, 측정을 안했기때문에, 0%로..)

 

이 상황을 막기위해 *.gradle에 아래와 같은 afterEvaluate 블록을 추가해, reports에도 등록되지 않도록 만들어 봅시다.

    jacocoTestReport {
        reports {
            html.required = true
            csv.required = true
            xml.required = true
        }
        finalizedBy 'jacocoTestCoverageVerification'
        dependsOn test

        afterEvaluate {
            classDirectories.setFrom(files(classDirectories.files.collect {
                fileTree(dir: it, exclude: ['제외할 클래스']
            }))
        }
    }

설정을 잘 했다면, 각 모듈별 reports에는 제외할 클래스가 성공적으로 보이지 않을건데, jacoco-aggregation으로 통합된 reports에는 여전히 보입니다.

 

이 문제는 글 작성 날 기준 미해결 이슈로 보입니다. 아니면 제작자가 의도적으로 이렇게 만든걸까요?

https://github.com/gradle/gradle/issues/20026

 

jacoco-report-aggregation should allow for exclusions · Issue #20026 · gradle/gradle

Expected Behavior When a subproject specifies a file or pattern to be excluded from the test coverage report (jacocoTestReport), the jacoco-report-aggregation should not include the specified file ...

github.com

 

하지만, 해결법은 있었는데, 다음과 같이 .gradle파일에 jacoco-aggregation에서도 제거해주면 됩니다.

testCodeCoverageReport {
    getClassDirectories().setFrom(files(
            [project(':application'),
             project(':utilities'),
             project(':list'),
            ].collect {
                it.fileTree(dir: "${it.buildDir}/classes/java/main", exclude: [
                        'foo/*',
                        'bar/*',
                ])
            }
    ))
}

Jacoco... 자동화를 시켜보자

위 설정들에는 아직 귀찮은점이 많은데..

1. 새로운 모듈이 생성될때마다 찾아가서 jacoco-aggregation에 추가를 해줘야한다.

2. 제외할 클래스가 생기면, .gradle을 뒤져가며 모든 exclude와 block에 추가를 해줘야한다.

 

너무 귀찮습니다. 이 과정을 자동화 시켜봅시다.

 

우선, 새로운 모듈이 생성되면, 자동으로 감지해서, jacoco-aggregation대상으로 등록해 줄겁니다.

project(':support:jacoco') {
    apply plugin: 'jacoco-report-aggregation'

    testCodeCoverageReport {
        getClassDirectories().setFrom(files(
                allProjects
                        .collect {
                            it.fileTree(dir: "${it.buildDir}/classes/java/main", exclude:
                                    excludeFromCoverage.stream()
                                            .map(s -> s + ".class")
                                            .collect(Collectors.toList()))
                        })
        )
    }

    var allProjectsExcludeJacoco = allProjects.stream()
            .filter(p -> !p.getDisplayName().contains('jacoco')
                    && !p.getDisplayName().contains('root project'))
            .collect(Collectors.toList())

    dependencies {
        implementation allProjectsExcludeJacoco
    }

}

위 코드블록은

1. exclude 대상을 자동으로 감지해서, jacoco-aggregation에서 제거한다.

2. 모든 프로젝트를 jacoco dependencies에 자동으로 추가해 통합한다.

이 두 역할을 하고 있습니다.

 

핵심은 아래 두 블록입니다.

이런 방식으로, jacoco에서 제외할 클래스를 자동화 할 수 있습니다.

excludeFromCoverage.stream().map(s -> s + ".class").collect(Collectors.toList()))
allProjects.stream()
            .filter(p -> !p.getDisplayName().contains('jacoco')
                    && !p.getDisplayName().contains('root project'))
            .collect(Collectors.toList())

모든 코드는 다음 링크에서 확인가능하며, 작성하지 않은 테스트 커버리지 제외 자동화 또한 직접 찾아보는것도 재미있을것 같아요

https://github.com/depromeet/na-lab-server/blob/main/gradle/jacoco.gradle 

 

GitHub - depromeet/na-lab-server

Contribute to depromeet/na-lab-server development by creating an account on GitHub.

github.com