๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿš€ ํŒ (๊ธฐ์ˆ  ์ ์šฉ ๋ฐฉ๋ฒ• ๋“ฑ)/๐ŸŒฑ Spring

[Spring & Github] Pull Request๋ฅผ ํ•  ๋•Œ ๋งˆ๋‹ค ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ํ™•์ธํ•˜์ž (feat. Jacoco)

by dev_writer 2024. 10. 14.

์•ˆ๋…•ํ•˜์„ธ์š”. 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๋กœ ์„ค์ •ํ–ˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ๊ฒฐ๊ณผ, ์•„๋ž˜์ฒ˜๋Ÿผ ํ…Œ์ŠคํŠธ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์—ˆ์Œ์„ ๋ณผ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ์ค‘ ๋ฐœ์ƒํ•œ IllegalClassFormatException

 

์ด์— ๋Œ€ํ•ด ์Šคํƒ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ๋ฅผ ์ฐธ๊ณ ํ•˜๋‹ˆ, ์ž๋ฐ” ๋ฒ„์ „์ด jacoco์™€ ๋งž์ง€ ์•Š์„ ๋•Œ ์ด๋Ÿฐ ์ผ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์•Œ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

 

์‹ค์ œ๋กœ jacoco์˜ ๊นƒํ—ˆ๋ธŒ์— ์ง์ ‘ ๊ฐ€์…”์„œ ํƒœ๊ทธ๋ฅผ ๋ณด์‹œ๋ฉด, ๋ฒ„์ „์ด ๊พธ์ค€ํžˆ ์˜ฌ๋ผ์˜ค๊ณ  ๋ฒ„์ „์— ๋”ฐ๋ผ ์ง€์›๋˜๋Š” ์ž๋ฐ” ๋ฒ„์ „ ๋˜ํ•œ ๋‹ค๋ฆ„์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉ๋˜๋Š” Java 21์€ jacoco 0.8.11 ๋ถ€ํ„ฐ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

 

๋”ฐ๋ผ์„œ 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๋ฅผ ์ฒดํฌํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Github Workflow permissions

 

์ดํ›„ Pull Request๊ฐ€ ์‹คํ–‰๋˜๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์ด ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์›Œํฌํ”Œ๋กœ์šฐ ์‹คํ–‰ ํ›„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€

 

๋งˆ์ฃผ์ณค๋˜ ๋ฌธ์ œ 2: ๋‚ฎ์€ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€

์œ„์˜ ์‚ฌ์ง„์ฒ˜๋Ÿผ, ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๊ฐ€ ๋„ˆ๋ฌด ๋‚ฎ๊ฒŒ ๋‚˜์™”์Šต๋‹ˆ๋‹ค.

 

์‹ค์ œ ๋กœ์ปฌ์—์„œ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ํ™•์ธํ•ด ๋ณด๋ฉด, ๋ชปํ•ด๋„ 75% ๊ทผ์ฒ˜๋Š” ๋‚˜์™€์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋กœ์ปฌ์—์„œ ํ™•์ธํ•œ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€

 

๊ทธ๋Ÿฐ๋ฐ ์™œ ์ด๋ ‡๊ฒŒ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๊ฐ€ ๋‚ฎ์•˜์„๊นŒ์š”?

 

ํ…Œ์ŠคํŠธ๋ฅผ ํ•œ ํ›„ ์ œ๊ณต๋œ ๋ฌธ์„œ๋ฅผ ๋ณด๋‹ˆ, Querydsl๊ณผ ๊ด€๋ จ๋œ ์ฝ”๋“œ๋„ ํ…Œ์ŠคํŠธ์— ํฌํ•จ์‹œํ‚ค๊ณ  ์žˆ์—ˆ์Œ์„ ๋ณผ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

build/reports/jacoco/test/html์—์„œ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

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

 

๊ฒฐ๊ณผ

์ตœ์ข…์ ์œผ๋กœ ์•„๋ž˜์ฒ˜๋Ÿผ ์ •์ƒ์ ์ธ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์ตœ์ข… ์ƒ์„ฑ๋œ Jacoco์˜ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€

 

Reference