JaCoCo가 무엇인지에 대해서는 이전 글에서 다루었다. (링크: JaCoCo란?)
우아한 기술블로그에 연철님이 작성하신 Gradle 프로젝트에 JaCoCo 설정하기를 참고하여 적용하였다.
개발 환경
나는 Java 21
, SpringBoot 3.3.3
, Gradle 9.0
을 사용하고 있다.
1. build.gradle의 plugins 블럭에 jacoco 플러그인 추가
plugin {
...
id 'jacoco'
...
}
2. jacoco 블럭을 만들어서 버전, 테스트 결과 리포트를 저장할 경로 지정
속성으로는 toolVersion과 reportsDir이 있다.
- toolVersion: JaCoCo 버전 (필수)
- JaCoCo Releases (GitHub)에서 릴리즈된 버전 정보를 확인할 수 있다.
- reportsDir: 테스트 결과 리포트를 저장할 경로 (선택)
- default 값은
${project.reporting.baseDir}/jacoco
이다.
- default 값은
나는 이 글을 쓰는 시점에서 가장 최신 버전인 0.8.12
를 사용했다.
jacoco {
toolVersion = '0.8.12'
}
터미널에 ./gradlew build
를 입력하여 빌드하고 나면 build
디렉토리 하위에 jacoco
디렉토리가 생성된다.
> ./gradlew build
인텔리제이에서 Load Gradle Changes
버튼을 클릭하면 Tasks/verification
디렉토리 하위에 jacocoTestReport
태스크와 jacocoTestcoverageVerification
태스크가 생성된다.
- jacocoTestReport: 테스트에서 실행된 코드의 커비리지 데이터를 수집하고, 이를 바탕으로 HTML, XML, CSV 등의 형식으로 리포트를 생성한다.
- HTML 리포트: 웹 브라우저에서 볼 수 있는 시각적 리포트
- XML 리포트: CI/CD 파이프라인에서 분석하거나 다른 도구와 통합할 때 사용
- CSV 리포트: 간단한 텍스트 형시으로 커버리지 데이터를 출력
- jacocoTestCoverageVerification: 테스트 커버리지 기준을 검증하는 역할을 하며, 특정 커버리지 기준에 충족되지 않으면 빌드가 실패하도록 설정할 수 있다.
- 커버리지 기준 검증: 프로젝트의 클래스, 패키지, 메서드 단위에서 명령어 커버리지, 라인 커버리지 등의 기준을 설정하고, 이를 기반으로 검증 수행
- 빌드 실패 조건 설정: 커버리지가 특정 기준을 충족하지 않으면 빌드를 실패시키는 규칙을 설정할 수 있다.
3. jacocoTestReport 태스크 구성
jacocoTestReport {
reports { // 리포트의 형식과 생성 여부 설정
html.required = true // HTML 리포트 생성
xml.required = false // XML 리포트 생성하지 않음
csv.required = false // CSV 리포트 생성하지 않음
}
afterEvaluate { // Gradle 빌드 평가가 끝난 후 실행되는 블록이다.
// JaCoCo 커버리지 리포트에 포함될 클래스 파일 디렉토리 설정
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it,
exclude: [ // 커버리지에서 제외할 파일 패턴 설정
'**/*Application*', // Application 이름이 포함된 파일 제외
'**/*Exception*', // Exception 이름이 포함된 파일 제외
'**/*Request*', // Request 이름이 포함된 파일 제외
'**/*Response*', // Response 이름이 포함된 파일 제외
'**/*Dto*', // DTO 이름이 포함된 파일 제외
'**/configuration/**' // configuration 디렉토리 하위 모든 파일 제외
]
)
}))
}
}
4. jacocoTestCoverageVerification 태스크 구성
jacocoTestCoverageVerification {
violationRules {
rule {
// 검증할 대상 설정
element = 'METHOD'
// 커버리지 기준 설정
limit {
counter = 'BRANCH' // 커버리지를 측정하는 기준
value = 'COVEREDRATIO' // 커버리지 계산에 사용할 값
minimum = 0.50 // 커버리지 값의 최소 허용 값 (지정한 커버리지가 충족되지 않으면 빌드 실패)
}
limit {
counter = 'LINE'
value = 'COVEREDRATIO'
minimum = 0.70
}
excludes = [
'*.*Application*',
'*.*Exception*',
'*.*Request*',
'*.*Response*',
'*.*Dto*',
'*.configuration.*',
]
}
}
}
규칙에는 element, enabled, includes, excludes, limits 등 다양한 속성을 추가할 수 있다.
자세한 내용은 JacocoViolationRule 문서 링크에서 확인할 수 있다.
element (ElementType 문서): 커버리지를 검증할 코드 단위 정의
- BUNDLE (default): 패키지 번들
- CLASS: 클래스
- GROUP: Bundle의 논리적 그룹
- METHOD: 메서드
- PACKAG: 패키지
- SOURCEFILE: 소스파일
includes: rule을 적용할 대상 정의
- 와일드카드(
*
및?
) 사용 가능 - 기본값은
[*]
(모든 요소 포함)
excludes: rule에서 제외할 대상 정의
- 와일드카드(
*
및?
) 사용 가능 - 기본값은
[ ]
(빈 리스트) (아무것도 제외하지 않음)
limit (JacocoLimit 문서): 커버리지 기준 설정 (세부 속성: counter, value, maximum, minimum)
- counter (CounterEntity 문서): 커버리지를 측정하는 기준
- INSTRUCTION (default): Java 바이트코드 명령 수
- LINE: 빈 줄을 제외한 실제 코드 라인 수 (라인이 한 번이라도 실행되면 실행된 것으로 간주)
- BRANCH: 조건문 분기 수
- METHOD: 메서드 수 (메서드가 한 번이라도 실행되면 실행된 것으로 간주)
- CLASS: 클래스 수 (내부 메서드가 한 번이라도 실행된다면 실행된 것으로 간주)
- COMPLEXITY: 순환 복잡도
- value (CounterValue 문서): 커버리지 계산에 사용할 값
- COVEREDRATIO (default): 전체 항목 대비 커버된 항목의 비율
- MISSEDRATIO: 전체 항목 대비 커버되지 않은 항목의 비율
- COVEREDCOUNT: 커버된 항목의 수
- MISSEDCOUNT: 커버되지 않은 항목의 수
- TOTALCOUNT: 전체 항목의 수
- minimum: 커버리지 검증 기준의 최소값 (default는 null)
- maximum: 커버리지 검증 기준의 최대값 (default는 null)
5. 실행해보기
실행해보기에 앞서 테스트 용도의 간단한 클래스와 테스트 코드를 만들었다.
package com.simple.blog.calculator;
public class Calculator {
public int add(int number1, int number2) {
return number1 + number2;
}
}
package com.simple.blog.calculator;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class CalculatorTest {
@Test
void addTest() {
Calculator calculator = new Calculator();
assertThat(calculator.add(1, 2)).isEqualTo(3);
}
}
./gradlew build
명령어로 gradle을 clean한 후
./gradlew build -x test
명령어로 test를 수행하지 않고 빌드를 해보면
아래와 같은 빌드 파일들을 볼 수 있다.
이 상태에서 ./gradlew jacocoTestReport
명령어를 수행해도 아무런 파일이 만들어지지 않는다.
테스트를 실행한 적이 없기 때문에 테스트 결과 파일이 없기 때문이다.
따라서, jacocoTestReport
태스크를 수행하기 위해서는 테스트 결과가 있어야 한다.
./gradlew test
명령어로 test 태스크를 수행해보자.
테스트 관련 빌드 파일이 생긴 것을 확인할 수 있다.
그 다음 ./gradlew jacocoTestReport
명령어로 jacocoTestReport
태스크를 수행해보자.
reports
디렉토리 하위에 jacoco
디렉토리가 생성된 것을 확인할 수 있다.
build/jacoco/test/html/index.html
에서 jacoco 수행 리포트를 확인할 수 있다.
./gradlew jacocoTestCoverageVerification
명령어를 이용해 커버리지를 검증해보자.
앞서 Cacluator 클래스와 메서드를 1개 만들었고, 해당 메서드에 대한 테스트가 존재하므로 빌드는 성공적으로 이루어진다.
테스트 코드를 삭제하고 다시 검증을 수행해보자.
테스트 코드 삭제 후 ./gradlew test
, ./gradlew jacocoTestReport
, ./gradlew jacocoTestCoverageVerification
명령어를 순서대로 수행하고 나면 설정한 minimum 조건에 충족하지 못했기 때문에 빌드가 실패한다.
지금도 겪었듯이, 이 상태에서는 jacoco를 사용하기 매우 불편하다.
jacoco 테스트 커버리지 기준을 통과했는지 알기 위해서는 항상 test
, jacocoTestReport
, jacocoTestCoverageVerification
태스크를 순서대로 모두 수행해야하기 때문이다.
설정한 기준의 테스트 커버리지를 달성했는지 알기 위해서는 jacocoTestCoverageVerification
태스크가 필요하고,
jacocoTestCoverageVerification
는 가장 최근의 jacocoTestReport
태스크 수행 결과를 기준으로 검증을 수행한다.
또, jacocoTestReport
를 만드려면 test
태스크를 수행한 결과가 있어야 한다.
따라서, 가장 최근 코드를 기준으로 항상 3가지 태스크를 순서대로 수행해주어야 한다.
이러한 불편함을 해결하기 위해 여러 태스크를 하나의 태스크로 묶어서 수행하도록 build.gradle
에 설정을 추가해보자.
6. 여러 개의 Task를 하나의 Task로 묶기
task 이름은 기존에 있는 task와 겹치지 않는다면 원하는 이름으로 지정할 수 있다.
나는 "jacoco"라는 이름으로 task를 만들 것이다.
build.gradle
에 아래 내용을 추가하고 Load Gradle Changes
(코끼리 버튼)을 누르면 gradle
의 verification
그룹에 jacoco
태스크가 추가된 것을 확인할 수 있다.
tasks.register('jacoco', Test) {
group 'verification'
description 'Runs the unit tests with coverage'
dependsOn(':test',
':jacocoTestReport',
':jacocoTestCoverageVerification')
tasks['jacocoTestReport'].mustRunAfter(tasks['test'])
tasks['jacocoTestCoverageVerification'].mustRunAfter(tasks['jacocoTestReport'])
}
./gradlew {태스크명}
명령어를 수행하면 명령어 하나로 세 개의 태스크를 모두 수행한 것을 확인할 수 있다.
7. 테스트 실행 시마다 jacoco 관련 태스크도 자동으로 수행하도록 설정
테스트 실행 시마다 jacoco 관련 태스크도 자동으로 수행하도록 설정하고 싶다면 gradle의 finalizedBy
를 사용하면 된다.
finalizedBy
를 사용하면 태스크 수행이 끝난 후 finalizedBy
로 지정한 태스크를 이어서 수행하도록 할 수 있다.
우리는 test
태스크에 이어서 jacocoTestReport
태스크와 jacocoTestReport
태스크를 수행시키고 싶은 것이므로,
test
태스크에 finalizedBy
항목을 추가하여 jacocoTestReport
태스크가 수행되도록 하고,
jacocoTestReport
태스크에 finalizedBy
항목을 추가하여 jacocoTestCoverageVerification
이 수행되도록 하면 된다.
test {
// ...
finalizedBy 'jacocoTestReport'
}
jacocoTestReport {
// ...
finalizedBy 'jacocoTestCoverageVerification'
}
나는 개발 중 테스트 수행 시마다 리포트 생성과 검증이 수행되는 것이 귀찮을 것 같아 굳이 추가하지는 않았다.
'Back-End > JAVA' 카테고리의 다른 글
Jacoco란? (0) | 2024.09.14 |
---|---|
try-with-resources란? (0) | 2023.04.05 |