์๋ ํ์ธ์. dev_writer์ ๋๋ค.
์ด๋ฒ ์๊ฐ์๋ ์ฝ๋ ์ปค๋ฒ๋ฆฌ์ง ๋๊ตฌ ์ค ํ๋์ธ Jacoco๋ฅผ ์ ์ฉํ ๊ณผ์ ์ ๋ํด ์๋ ค๋๋ฆฌ๊ฒ ์ต๋๋ค.
Jacoco
Jacoco๋ ์๋ฐ ์ฝ๋ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ์ธก์ ํ๊ณ ๋ฆฌํฌํ ํ ์ ์๋ ์คํ ์์ค ํดํท์ ๋๋ค. (์ํค๋ฐฑ๊ณผ) ์ ์ด์ Jacoco๋ผ๋ ๋จ์ด ์์ฒด๊ฐ Java code coverage tools๋ผ๋ ๋จ์ด๋ฅผ ๋ดํฌํ๊ณ ์์ต๋๋ค.
์ฝ๋ ์ปค๋ฒ๋ฆฌ์ง (code coverage)
์ฝ๋ ์ปค๋ฒ๋ฆฌ์ง๋ ์ํํธ์จ์ด์ ํ ์คํธ๋ฅผ ๋ ผํ ๋ ์ผ๋ง๋ ํ ์คํธ๊ฐ ์ถฉ๋ถํ๊ฐ๋ฅผ ๋ํ๋ด๋ ์งํ์ ๋๋ค. ์ํํธ์จ์ด ํ ์คํธ๋ฅผ ์งํํ์ ๋ ์ฝ๋ ์์ฒด๊ฐ ์ผ๋ง๋ ์คํ๋์๋์ง๋ฅผ ๋ปํฉ๋๋ค. - ์ํค๋ฐฑ๊ณผ
์ผ๋ฐ์ ์ผ๋ก ํ ์คํธ ์ฝ๋๋ฅผ ๊ผผ๊ผผํ๊ฒ ์์ฑํ ์๋ก ์์ธ ์ผ์ด์ค๋ค์ ์ ํ์งํ ์ ์์ผ๋ฉฐ, ๊ทธ๋ ๊ธฐ์ ์ํํธ์จ์ด์ ํ์ง์ ๋์ด๊ธฐ ์ํด์๋ ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง๊ฐ ๋๋ฌด ๋ฎ์ง ์์์ผ ํ ๊ฒ์ ๋๋ค.
๊ทธ๋ฌ๋ฉด Jacoco๋ฅผ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ํด ๋ณธ๊ฒฉ์ ์ผ๋ก ์์๋ณด๊ฒ ์ต๋๋ค.
1๋จ๊ณ: build.gradle์ jacoco ์ ์ฉ
ํ๋ก์ ํธ์ build.gradle์ jacoco๋ฅผ ์ ์ฉํฉ๋๋ค.
// 1๋จ๊ณ
plugins {
...
id 'jacoco'
}
// 2๋จ๊ณ
test {
...
finalizedBy jacocoTestReport
}
// 3๋จ๊ณ
jacoco {
toolVersion = "0.8.12"
}
// 4๋จ๊ณ
jacocoTestReport {
dependsOn test
reports {
xml.required.set(true)
}
}
- 1๋จ๊ณ: jacoco ํ๋ฌ๊ทธ์ธ์ ์ค์นํฉ๋๋ค.
- 2๋จ๊ณ: ํ ์คํธ๊ฐ ์๋ฃ๋ ํ jacocoTestReport๊ฐ ์คํ๋๋๋ก ํฉ๋๋ค.
- 3๋จ๊ณ: jacoco์ ๋ฒ์ ์ ์ค์ ํฉ๋๋ค.
- 4๋จ๊ณ: jacocoTestReport๊ฐ ์คํ๋๊ธฐ ์ ์ test๊ฐ ์คํ๋๋๋ก ์ค์ ํ๊ณ , ์ฝ๋ ์ปค๋ฒ๋ฆฌ์ง ๋ณด๊ณ ์๋ฅผ XML ํ์์ผ๋ก ์์ฑํ๋๋ก ์ค์ ํฉ๋๋ค. (XML ํ์์ผ๋ก ์์ฑํ๋ฉด, Pull Request์์ ์ปค๋ฒ๋ฆฌ์ง ๊ฒฐ๊ณผ๋ฅผ ํ๋ก ๋ณด๊ธฐ ์ฉ์ดํฉ๋๋ค.)
๋ง์ฃผ์ณค๋ ๋ฌธ์ 1: jacoco ๋ฒ์ ์ ํ์ธํ์
๊ธฐ์กด์๋ ์ฌ๋ฌ ๋ธ๋ก๊ทธ ๊ธ๋ค์ ์ฐธ๊ณ ํ์ฌ, jacoco ๋ฒ์ ์ 0.8.8๋ก ์ค์ ํ์์ต๋๋ค. ๊ทธ ๊ฒฐ๊ณผ, ์๋์ฒ๋ผ ํ ์คํธ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์์์ ๋ณผ ์ ์์์ต๋๋ค.
์ด์ ๋ํด ์คํ์ค๋ฒํ๋ก์ฐ๋ฅผ ์ฐธ๊ณ ํ๋, ์๋ฐ ๋ฒ์ ์ด jacoco์ ๋ง์ง ์์ ๋ ์ด๋ฐ ์ผ์ด ๋ฐ์ํ ์ ์๋ค๋ ์ฌ์ค์ ์๊ฒ ๋์์ต๋๋ค.
์ค์ ๋ก jacoco์ ๊นํ๋ธ์ ์ง์ ๊ฐ์ ์ ํ๊ทธ๋ฅผ ๋ณด์๋ฉด, ๋ฒ์ ์ด ๊พธ์คํ ์ฌ๋ผ์ค๊ณ ๋ฒ์ ์ ๋ฐ๋ผ ์ง์๋๋ ์๋ฐ ๋ฒ์ ๋ํ ๋ค๋ฆ์ ๋ณผ ์ ์์ต๋๋ค.
๋ฐ๋ผ์ 24๋ 10์ ๊ธฐ์ค ๊ฐ์ฅ ๋ง์ง๋ง์ผ๋ก ์ฌ๋ผ์จ ๋ฒ์ ์ธ 0.8.12๋ก ์ ๊ทธ๋ ์ด๋ํ๋, ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋ ์ ์์์ต๋๋ค.
2๋จ๊ณ: github actions ์คํฌ๋ฆฝํธ ์์ฑ
jacoco ์ปค๋ฒ๋ฆฌ์ง ์งํ๋ฅผ Pull Request์ ๋จ๊ธฐ๊ธฐ ์ํด github actions ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํฉ๋๋ค.
name: Jacoco Test Coverage Check
// 1๋จ๊ณ
on:
pull_request:
types: [opened, synchronize, reopened]
// 2๋จ๊ณ
permissions: write-all
// 3๋จ๊ณ
jobs:
test:
runs-on: ubuntu-latest
steps:
// 3-1๋จ๊ณ
- uses: actions/checkout@v3
with:
fetch-depth: 0
// 3-2๋จ๊ณ
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: '21'
// 3-3๋จ๊ณ
- name: Create and inject jasypt secret into application-secret.properties
run: |
touch ./src/main/resources/application-secret.properties
echo "ENCRYPT_KEY=${{ secrets.ENCRYPT_KEY }}" > ./src/main/resources/application-secret.properties
cat ./src/main/resources/application-secret.properties
// 3-4๋จ๊ณ
- name: Grant execute permission for gradlew
run: chmod +x gradlew
// 3-5๋จ๊ณ
- name: Test with Gradle
run: |
export ENCRYPT_KEY=${{ secrets.ENCRYPT_KEY }}
./gradlew --info test
// 3-6๋จ๊ณ
- name: Test Coverage Report
id: jacoco
uses: madrapps/jacoco-report@v1.7.1
with:
title: Test Coverage Report
paths: ${{ github.workspace }}/build/reports/jacoco/test/jacocoTestReport.xml
token: ${{ secrets.GITHUB_TOKEN }}
min-coverage-overall: 30
min-coverage-changed-files: 50
- 1๋จ๊ณ: Pull Request๊ฐ ์ด๋ฆฌ๊ฑฐ๋, ์ ๋ฐ์ดํธ๋๊ฑฐ๋, ๋ค์ ์ด๋ฆด ๋๋ง๋ค ์ํฌํ๋ก์ฐ๊ฐ ์คํ๋๋๋ก ํฉ๋๋ค.
- 2๋จ๊ณ: ๋ชจ๋ ๋ฒ์์ ๋ํด ์ฐ๊ธฐ ๊ถํ์ ๋ถ์ฌํฉ๋๋ค. Pull Request์ ์ฝ๋ฉํธ์ ์ฐ๊ธฐ ์ํด ํ์ํฉ๋๋ค.
- 3-1๋จ๊ณ: actions/checkout ์ก์ ์ ์ฌ์ฉํ์ฌ ํ์ฌ ๋ฆฌํฌ์งํฐ๋ฆฌ์ ์ฝ๋๋ฅผ ์ฒดํฌํ๊ณ , fetch-depth: 0์ ์ค์ ํ์ฌ ๋ชจ๋ ์ปค๋ฐ ๊ธฐ๋ก์ ๊ฐ์ ธ์ต๋๋ค.
- 3-2๋จ๊ณ: JDK๋ฅผ ์ค์ ํฉ๋๋ค.
- 3-3๋จ๊ณ: Jasypt ํค๋ฅผ ENCRYPT_KEY๋ก ๊ด๋ฆฌํ๊ณ ์์๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ ์ฃผ์ ํด ์ฃผ๋ ๋จ๊ณ์ ๋๋ค.
- 3-4๋จ๊ณ: gradlew ์คํ ๊ถํ์ ๋ถ์ฌํฉ๋๋ค.
- 3-5๋จ๊ณ: ENCRYPT_KEY๋ฅผ ์ฃผ์ ํ ๋ค ํ ์คํธ๋ฅผ ์คํํฉ๋๋ค. (info ํ๋๊ทธ๋ ์คํ ์ ๋ณด ์ถ๋ ฅ์ ์ ๊ณตํฉ๋๋ค.)
- 3-6๋จ๊ณ: Jacoco ํ
์คํธ ์ปค๋ฒ๋ฆฌ์ง ๋ณด๊ณ ์๋ฅผ ์์ฑํ๋ ํต์ฌ ๋จ๊ณ์
๋๋ค. github.workspace์ secrets.GITHUB_TOKEN์ ๊นํ๋ธ ์ก์
์์ ์๋์ผ๋ก ์ ๊ณตํด ์ค๋๋ค. ๋ํ, ์ ์ฒด ํ์ผ์ ์ต์ ์ปค๋ฒ๋ฆฌ์ง (min-coverage-overall)์ ๋ณ๊ฒฝ๋ ํ์ผ์ ์ต์ ์ปค๋ฒ๋ฆฌ์ง (min-coverage-changed-files)๋ฅผ ์ค์ ํ ์๋ ์์ต๋๋ค. (๋ ๊ฐ ๋ชจ๋ ๊ธฐ๋ณธ๊ฐ์ 80%์
๋๋ค.)
- jacocoTestReport์์ path๋ฅผ ๋ณ๋๋ก ์ง์ ํ ์ ์์ผ๋ฉฐ, ๋ณ๋๋ก ์ง์ ํ์ง ์์๋ค๋ฉด ์์ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค.
- uses์ ๋ฒ์ ์ ์ฌ๊ธฐ์์ ํ์ธํ์ค ์ ์์ต๋๋ค.
๋ค์์ผ๋ก๋ ๊นํ๋ธ ๋ฆฌํฌ์งํฐ๋ฆฌ์์ Settings > Actions > General > Workflow permissions์ ๋ค์ด๊ฐ Read and write permissions๋ฅผ ์ฒดํฌํด์ค์ผ ํฉ๋๋ค.
์ดํ Pull Request๊ฐ ์คํ๋๋ฉด, ์๋์ ๊ฐ์ด ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
๋ง์ฃผ์ณค๋ ๋ฌธ์ 2: ๋ฎ์ ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง
์์ ์ฌ์ง์ฒ๋ผ, ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง๊ฐ ๋๋ฌด ๋ฎ๊ฒ ๋์์ต๋๋ค.
์ค์ ๋ก์ปฌ์์ ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ํ์ธํด ๋ณด๋ฉด, ๋ชปํด๋ 75% ๊ทผ์ฒ๋ ๋์์ผ ํ์ต๋๋ค.
๊ทธ๋ฐ๋ฐ ์ ์ด๋ ๊ฒ ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง๊ฐ ๋ฎ์์๊น์?
ํ ์คํธ๋ฅผ ํ ํ ์ ๊ณต๋ ๋ฌธ์๋ฅผ ๋ณด๋, Querydsl๊ณผ ๊ด๋ จ๋ ์ฝ๋๋ ํ ์คํธ์ ํฌํจ์ํค๊ณ ์์์์ ๋ณผ ์ ์์์ต๋๋ค.
Qํด๋์ค์ ๋ํ ํ์ผ๋ค๋ Jacoco๊ฐ ์์งํ๋ค ๋ณด๋, ์ ํํ ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง๊ฐ ์ธก์ ์ด ์ ๋ ๊ฒ์ด์์ต๋๋ค.
๋ฐ๋ผ์ Qํด๋์ค๋ฅผ ์ ๊ฑฐํ๊ธฐ ์ํด jacocoTestReport๋ฅผ ์๋์ฒ๋ผ ์์ ํ์ต๋๋ค.
jacocoTestReport {
dependsOn test
reports {
xml.required.set(true)
}
afterEvaluate {
classDirectories.setFrom(
files(classDirectories.files.collect {
fileTree(dir: it, exclude: [
'**/Q*.class',
'**/com/flab/eattofit/EattofitApplication.class'
])
})
)
}
}
๋ง์ง๋ง์ ๋ถ์ ํด๋์ค๋ ์คํ๋ง์ ๋ฉ์ธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ปํฉ๋๋ค. ์ ์ธํ์ง ์์ ๊ฒฝ์ฐ Qํด๋์ค์ฒ๋ผ ํ ์คํธ ๋ฒ์ฃผ์ ํฌํจ๋์ด ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ๋ฎ์ถ๋ ์์ธ์ด ๋๊ธฐ ๋๋ฌธ์ ๋ถ์์ต๋๋ค.
lombok.config ์์ฑ
๋ง์ง๋ง์ผ๋ก, lombok์ด ์์ฑํ ๋ฉ์๋ (@Getter, @Setter..)์ ๋ํด์๋ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ํฌํจํ์ง ์๊ธฐ ์ํด์๋ ํ๋ก์ ํธ ๋ฃจํธ ๋จ์ lombok.config๋ฅผ ์ถ๊ฐํ์ฌ ์๋์ฒ๋ผ ์์ฑํ๋ฉด ๋ฉ๋๋ค.
lombok.addLombokGeneratedAnnotation = true
๊ฒฐ๊ณผ
์ต์ข ์ ์ผ๋ก ์๋์ฒ๋ผ ์ ์์ ์ธ ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ํ๋ณดํ ์ ์์์ต๋๋ค.