공부
[단위 테스트] JaCoCo + CI Github Actions 배지 생성하기
nahowo
2025. 2. 6. 00:07
JaCoCo 프로젝트 적용
- build.gradle
- plugins { id 'java' id 'org.springframework.boot' version '3.2.3' id 'io.spring.dependency-management' version '1.1.4' id 'org.hibernate.orm' version '6.4.4.Final' id 'org.graalvm.buildtools.native' version '0.9.28' id 'jacoco' } test { finalizedBy jacocoTestReport } jacocoTestReport { dependsOn test reports { xml.required = true html.required = true } } jacoco { toolVersion = "0.8.13" }
- 위처럼 작성한 뒤 빌드 후 테스트를 수행했더니 아래와 같은 오류가 발생했다.
- Execution failed for task ':test'. > Could not resolve all files for configuration ':jacocoAgent'. > Could not find org.jacoco:org.jacoco.agent:0.8.13. Required by: project : Possible solution: - Declare repository providing the artifact, see the documentation at <https://docs.gradle.org/current/userguide/declaring_repositories.html>
- 더 찾아보고 추가로 build.gradle 파일을 작성해 주었다.
JaCoCo와 JDK 버전이 맞지 않는다는 글이 있어서 0.8.8로 버전을 변경했다.- Release note를 보니까 JDK 17은 0.8.8부터 지원한다고 한다.
- plugins { id 'jacoco' } jacoco { toolVersion = "0.8.8" } repositories { mavenCentral() } test { useJUnitPlatform() finalizedBy jacocoTestReport } jacocoTestReport { dependsOn test reports { xml.required = true html.required = true } } dependencies { // Test testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' }
- 그러면 천천히 설정을 살펴보고 커스텀해보자.
build.gradle 설정
- repositories 공식 문서: https://docs.gradle.org/current/userguide/declaring_repositories.html
- gradle은 프로젝트에서 사용할 dependencies를 다운로드받을 위치를 명시해야 한다. mavenCentral() 설정은 퍼블릭 메이븐 레포지토리에서 해당 dependencies를 다운받을 수 있다는 것을 의미한다.
- 실제 퍼블릭 메이븐 레포지토리에 들어가 보니까 다운받을 수 있는 JaCoCo Agent 버전이 나열되어 있었다.
- 오류 메시지는 Could not find org.jacoco:org.jacoco.agent:0.8.13. 인데, 버전 중 0.8.13은 없었다… 버전도 잘못되었고 어디서 다운받을지도 명시해주지 않아서 생긴 문제인 듯.
- repositories { mavenCentral() }
- test 공식 문서: https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html
- test는 JUnit이나 TestNG 테스트를 실행한다.
- useJUnitPlatform()은 JUnit 플랫폼 기반 테스트들을 탐색하고 실행하도록 명시한다.
- Specifies that JUnit Platform should be used to discover and execute the tests.
- finalizedBy 공식 문서: https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.api/-task/finalized-by.html
- 태스크 체이닝 방법 중 하나이다. finalizedBy [태스크] 라고 작성하면 해당 태스크 이후에 동작한다.
- 추가로 dependsOn [태스크] 라고 작성하면 해당 태스크와 함께 동작한다.
- test { useJUnitPlatform() finalizedBy jacocoTestReport }
- jaCoCoTestReport - 태스크
- 위에서 설명했듯이 test와 함께 동작한다.
- reports는 ReportContainer의 인스턴스이다. 리포트의 반환 타입을 설명한다. required는 생성 여부를 결정하는 플래그이다(이전에는 enabled로 쓰였다고 한다. 여러 예시들에서 enabled만 나와 있길래 헷갈렸다).
- xml은 현재 별로 필요가 없을 것 같아서 false로 변경해 두었다.
- jacocoTestReport { dependsOn test reports { xml.required = true html.required = true } }
- dependencies 공식 문서: https://docs.gradle.org/current/userguide/declaring_dependencies.html → 따로 정리가 필요할 것 같아서 포스트를 따로 작성었다.
- dependencies { // Test testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' }
- 최종적인 build.gradle은 아래와 같다.
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.3'
id 'io.spring.dependency-management' version '1.1.4'
id 'org.hibernate.orm' version '6.4.4.Final'
id 'org.graalvm.buildtools.native' version '0.9.28'
id 'jacoco'
}
jacoco {
toolVersion = "0.8.12"
}
repositories {
mavenCentral()
}
test {
useJUnitPlatform()
finalizedBy jacocoTestReport
}
jacocoTestReport {
dependsOn test
reports {
xml.required = false
html.required = true
}
}
group = 'com.Alchive'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
targetCompatibility = '17'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'mysql:mysql-connector-java:8.0.32'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.mapstruct:mapstruct:1.5.3.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final'
// OAuth
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
// JWT
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
// Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
// Validation - @NotBlank
implementation 'org.springframework.boot:spring-boot-starter-validation'
// monitoring
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
// Slack API
implementation 'com.slack.api:bolt:1.18.0'
implementation 'com.slack.api:bolt-servlet:1.18.0'
implementation 'com.slack.api:bolt-jetty:1.18.0'
implementation 'com.slack.api:slack-api-client:1.44.1'
// Discord API
implementation 'net.dv8tion:JDA:5.0.0-beta.5'
// // Test
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
}
hibernate {
enhancement {
enableAssociationManagement = true
}
}
Github Actions CI
- 엄청나게 오래 삽질하면서 작성한 CI 파이프라인…
name: CI/CD
on:
push:
branches:
- develop
pull_request:
branches:
- '**'
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
# 기본 체크아웃
- name: Checkout
uses: actions/checkout@v3
# Gradlew 실행 허용
- name: Run chmod to make gradlew executable
run: chmod +x ./gradlew
# JDK 17 세팅
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
# 환경 변수 설정
- name: Set environment values
run: |
touch ./src/main/resources/env.properties
echo "${{ secrets.ENV_PROPERTIES }}" > ./src/main/resources/env.properties
shell: bash
# 테스트 수행
- name: Run Tests
run: ./gradlew test
# JaCoCo 배지 생성
- name: Generate JaCoCo Badge
uses: cicirello/jacoco-badge-generator/@v2
with:
generate-branches-badge: true
jacoco-csv-file: build/reports/jacoco/test/jacocoTestReport.csv
# JaCoCo 배지 깃허브 업로드
- name: Upload JaCoCo Badge
run: |
if [[ -n "$(git status --porcelain .github/badges)" ]]; then
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@github.com'
git add .github/badges/*
git commit -m "Update JaCoCo Badge"
git push origin develop
fi
# Gradle build
- name: Build with Gradle
uses: gradle/gradle-build-action@v2
with:
arguments: clean build -x test
steps의 name 기준으로 과정을 설명한다.
- 이전까지의 과정은 기존 CICD 과정과 동일하다.
Run Tests
- ./gradlew test를 사용해 테스트를 돌린다.
- 로컬 터미널에서 ./gradlew test 명령어를 수행하니까 알 수 없는 명령어라고 나왔다. 구글링을 해서 이 글을 보니까 os별 개행문자 차이 때문에 생기는 문제였다. 프로젝트 파일을 맥 로컬에서 직접 작성하지 않고 스프링 프로젝트 이니셜라이저로 하다 보니까 가끔 개행문자 오류가 생긴다…
- 아무튼 vi gradlew로 파일을 열고, :set fileformat=unix를 작성한 뒤 저장하니까 해결되었다.
- 로컬에서 잘 돌아가는지 확인한 뒤 커밋하는 것을 추천한다… 괜히 github actions 기다리고 커밋 메시지만 쌓이는 것보다 나음 ㅎㅎ…
Generate JaCoCo Badge
- 나는 cicirello/jacoco-badge-generator를 깃허브 마켓플레이스에서 찾아 사용했다. 사용 방법은 깃허브 페이지에 자세히 나와 있다.
- 분기 커버리지 배지를 생성하고, 스프링 JaCoCo 의존성에서 생성하도록 지정한 csv 파일의 위치도 명시한다.
Upload JaCoCo Badge
- 처음에 위의 과정까지만 하니까 Actions 과정 내에서만 배지 이미지를 생성하고, 실제 내 프로젝트 레포지토리에는 배지 이미지가 생성되지 않았다. 같은 배지를 사용한 다른 레포지토리를 참조해서 배지 이미지를 커밋하도록 하는 로직을 추가했다.
- 이렇게 ci를 다 작성했다! 전문은 이 링크에 있다.
배지 리드미에 업로드
- 레포지토리에 업로드된 배지 이미지를 리드미에 추가해주기만 하면 된다. [] ← 아까 업로드했던 svg 파일의 경로를 지정한다.
- 그러면 이렇게 커버리지 지표가 표시된다. 지금은 작성한 테스트 코드가 작아서 2.2%밖에 안 된다 ㅎㅎ…