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 태스크는 일단 별도의 수정 없이 그냥 사용해도 무방하지만 필자는 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'.