최근에 진행 중인 2개의 프로젝트에 jacoco와 sonar cloud를 적용했다.
sonar cloud만 적용할 경우 아래와 같이 테스트 커버리지 값은 노출되지 않는다.
AS IS
jacoco를 활용해 테스트 커버리지를 측정하고 해당 측정 값을 소나클라우드로 전송해 소나클라우드 상에서도 테스트 커버리지를 함께 다룰 수 있는 방법을 소개하려고 한다.
TO BE
1. 전체 흐름
먼저 작업을 들어가기 전에 전체 작업 맥락은 다음과 같다.
jacoco를 통해 테스트 커버리지를 측정하고 측정 값을 xml 파일로 저장한다.
프로젝트 test 실행시 jacoco 테스트 커버리지가 측정 되며 지정된 경로에 xml 파일로 결과가 저장된다.
저장한 xml 파일을 소나클라우드로 전송한다.
소나클라우드에서는 해당 값을 활용해 테스트 커버리지 값을 매핑하고 다른 정적 검사 지표들과 함께 노출시킨다.
필자는 ci 스크립트를 활용해 main, develop 브랜치로 풀리퀘를 보낼 때 소나클라우드 검사 결과가 comment와 같이 뜨도록 설정하고자한다.
또한, 예시 코드는 코틀린으로 사용 중인 언어에 맞게 아래 코드들을 수정해 사용하면 된다.
2. jacoco 연동
jacoco란?
자바 코드 커버리지 측정 오픈 소스 라이브러리
build.gradle에 관련 연동 정보 추가
plugins {
id("jacoco")
}
멀티 모듈 사용 중으로 subprojects 블록에 아래 jacoco 관련 설정 내용을 추가해준다.
subprojects {
apply(plugin = "jacoco")
tasks.test { //test 실행 시 jacocoTestReport 실행
finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run
}
tasks.jacocoTestReport {
dependsOn(tasks.test) // tests are required to run before generating the report
reports {
xml.required.set(true) // xml 설정
xml.outputLocation.set(File("$buildDir/reports/jacoco.xml")) //xml로 output
}
classDirectories.setFrom(
files(
classDirectories.files.map {
fileTree(it) { // 테스트 커버리지 측정 제외 목록
exclude(
"**/*Application*",
"**/*Config*",
"**/*Dto*",
"**/*Request*",
"**/*Response*",
"**/*Interceptor*",
"**/*Exception*"
//필요한 제외 목록 더 추가하시면 됩니다.
)
}
}
)
)
}
}
test 실행 시 jacocoTestReport가 실행된다.
해당 결과 레포트를 xml로 받으며 파일의 저장 위치를 지정해줬다.
이때, 테스트 커버리지 측정에서 제외할 목록들 또한 설정해줬다.
테스트 실행시 지정된 경로에 측정 값 잘 저장되는 거 확인 가능
3. 소나클라우드 가입 및 설정
소나클라우드에 가입하여 자신이 작업 중인 조직과 레포지토리를 생성한다.
quality gate 생성을 통해 팀원들과 test coverage 통과 비율과 같은 수치들을 논의해 생성 후 프로젝트와 매핑하면 된다.
생성안할시 sonar way가 기본 디폴트 quality gate이며 해당 값들은 아래와 같다.
소나클라우드는 기본적으로 자동 검사를 지원한다. 필자는 ci 스크립트를 통해 분석할 것이기 때문에 자동 검사 옵션을 꺼줘야한다.
소나클라우드 레포지토리->administration->analysis method에서 비활성화한다.
이때, 레포지토리에서 administration이 안뜬다면 조직에 대한 권한이 없는거다.
조직 권한자에 부탁해 권한을 받은 후 추가적으로 조직->administration->groups->owners에서 자신의 소나클라우드 계정을 추가해야지만 administration 설정창이 뜬다.
analysis method를 비활성화하지 않고 스크립트에서도 실행할 경우 충돌이 일어나며 깃헙 액션즈 fail이 발생하므로 옵션을 꼭!! 꺼줘야한다.
소나클라우드 security 페이지에서 토큰을 생성한다.
해당 토큰은 ci 스크립트에 들어가는 토큰으로 깃헙 액션즈에 넣어야하므로 잘 기억해두자.
4. sonar cloud 연동
build.gradle에 관련 연동 정보 추가
plugins {
id("org.sonarqube") version "4.2.1.3168"
}
sonarqube {
properties {
property("sonar.projectKey", "프로젝트명")
property("sonar.organization", "조직명")
property("sonar.host.url", "https://sonarcloud.io")
// sonar additional settings
property("sonar.sources", "src")
property("sonar.language", "Kotlin")
property("sonar.sourceEncoding", "UTF-8")
property("sonar.test.inclusions", "**/*Test.java")
property("sonar.exclusions", "**/test/**, **/Q*.kt, **/*Doc*.kt, **/resources/** ,**/*Application*.kt , **/*Config*.kt, **/*Dto*.kt, **/*Request*.kt, **/*Response*.kt ,**/*Exception*.kt ,**/*ErrorCode*.kt")
property("sonar.java.coveragePlugin", "jacoco")
}
}
subprojects {
sonarqube {
properties {
property("sonar.java.binaries", "$buildDir/classes")
//아래에 jacoco 연동시 지정해줬던 경로를 넣어줘야한다.
property("sonar.coverage.jacoco.xmlReportPaths", "$buildDir/reports/jacoco.xml")
}
}
}
5. ci 스크립트 작성
on:
pull_request:
branches: [ "main", "develop" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Cache SonarCloud packages
uses: actions/cache@v3
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Gradle packages
uses: actions/cache@v3
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: ${{ runner.os }}-gradle
- name: test and analyze
run: ./gradlew test sonar --info --stacktrace --no-daemon
env:
GITHUB_TOKEN: ${{ secrets.GIT_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
깃헙 토큰 발급 방법
프로필 사진 -> settings -> developer settings -> personal access tokens
레포지토리 settings에서 소나토큰과 깃헙 토큰을 넣어준다.
+ caused by: java.lang.outofmemoryerror: metaspace 에러 발생시
한 프로젝트에서는 ci 스크립트에 sonar 실행 시 oom 오류가 발생했고 한 프로젝트는 정상 작동했다.
프로젝트에 따라 heap 영역 메모리 사용량이 달라 발생했던 문제로
문제가 발생했던 프로젝트의 sonar 실행 명령어에 -Dorg.gradle.jvmargs="-Xmx2g" 옵션을 추가해 메모리를 늘려 해결했다.
ex. ./gradlew sonar -Dorg.gradle.jvmargs="-Xmx2g" --stacktrace --no-daemon
프로젝트 gradlew 설정에서 프로젝트의 -xmx -xms 값을 늘려 해결하는 방법도 있었는데 sonar task만을 위해 확장하는게 효율적이지 않다 생각하여 스크립트 상에서 soanr 실행시에만 메모리를 늘려 해결하는 방식을 택했다.
참고 사이트
https://docs.sonarcloud.io/advanced-setup/ci-based-analysis/github-actions-for-sonarcloud/
https://techblog.woowahan.com/2661/