[DevOps] JAVA Gradle JaCoCo (Code coverage) ์„ค์ •ํ•˜๊ธฐ

2022. 9. 21. 13:53ใ†System ์ž‘์—…์‹ค/DevOps

728x90
๋ฐ˜์‘ํ˜•




๋ณธ ๋‚ด์šฉ์— ๋Œ€ํ•œ Source Code๋Š” ์ด ๊ณณ์—์„œ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์–ด์š”.




๐Ÿ—‚ ๋ชฉ์ฐจ

โš ๏ธ ์•„๋ž˜ ๋ชฉ์ฐจ ์ค‘ ๋ช‡๋ช‡๊ฐœ์˜ ๋งํฌ๊ฐ€ ๊ฑธ๋ฆฌ์ง€ ์•Š๋Š” ๋ฌธ์ œ๋กœ ๊ธ€ ๋งจ ํ•˜๋‹จ์— ๋‹ค์Œ ๊ธ€๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด ๋‘์—ˆ์Šต๋‹ˆ๋‹ค.

โœ… [CI/CD] Jenkins์™€ Gitea ์—ฐ๋™
โœ… [CI/CD] Jenkins Trigger ์ •๋ณด Discord๋กœ ๋ณด๋‚ด๊ธฐ
โœ… [CI/CD] ์ •์  ์ฝ”๋“œ ๋ถ„์„ ํˆด SonarQube์™€ Jenkins ์—ฐ๋™
โœ… [CI/CD] SonarQube๋ฅผ ํ†ตํ•ด Code Convention ์ ์šฉ
โœ… [DevOps] JAVA Gradle JaCoCo (Code coverage) ์„ค์ •ํ•˜๊ธฐ 
โœ… [DevOps] JAVA Gradle JaCoCo (Code coverage) ์„ค์ •ํ•˜๊ธฐ (์ถ”๊ฐ€)(https://junyharang.tistory.com/392)

โœ… [CI/CD] Jenkins + Docker๋ฅผ ์ด์šฉํ•œ ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ - โ‘  Application Linuxt(Ubuntu)์— SSH๋ฅผ ์ด์šฉํ•œ ํŒŒ์ผ ์ „์†ก
โœ… [CI/CD] Jenkins + Docker๋ฅผ ์ด์šฉํ•œ ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ - โ‘ก Create Docker Image And BackUp
โœ… [CI/CD] Jenkins + Docker๋ฅผ ์ด์šฉํ•œ ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ - โ‘ข Application Server Docker Job (โ‘  Application ๋„์ปค ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ)
โœ… [CI/CD] Jenkins + Docker๋ฅผ ์ด์šฉํ•œ ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ - โ‘ข Application Server Docker Job (โ‘ก Application Docker Run)(https://junyharang.tistory.com/406)
โœ… [CI/CD] Jenkins + Docker๋ฅผ ์ด์šฉํ•œ ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ - โ‘ข Application Server Docker Job (โ‘ข Application Docker Health Check)
โœ… [CI/CD] Jenkins + Docker๋ฅผ ์ด์šฉํ•œ ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ - โ‘ฃ NGINX Server Docker Job (โ‘  NGINX ๊ฐ ์ข… ์„ค์ •) 
โœ… [CI/CD] Jenkins + Docker๋ฅผ ์ด์šฉํ•œ ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ - โ‘ฃ NGINX Server Docker Job (โ‘ก NGINX Docker ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ) 
โœ… [CI/CD] Jenkins + Docker๋ฅผ ์ด์šฉํ•œ ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ - โ‘ฃ NGINX Server Docker Job (โ‘ข NGINX Docker Run & Health Check)
โœ… [CI/CD] Jenkins + Docker๋ฅผ ์ด์šฉํ•œ ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ - โ‘ฃ NGINX Server Docker Job (โ‘ข NGINX ์žฌ ์„ค์ •)



๐Ÿค” ๋‚ด๊ฐ€ ๋งŒ๋‚œ ๋ฌธ์ œ

โ›”๏ธ [Jenkins] java.lang.OutOfMemoryError: Java heap space

 

 

 

๐Ÿš€ JaCoCo ์„ค์ •

    ๐Ÿ”ฝ  ๊ฐœ์š”

        ๐Ÿ“ฆ JaCoCo๋ž€?

JaCoCo๋Š” Java Code์˜ Coverage๋ฅผ Checkํ•˜๋Š” Plugin์ด์—์š”.
Test Code๋ฅผ ์ง„ํ–‰ํ•˜๊ณ , ํ•ด๋‹น Test์˜ Coverage ๊ฒฐ๊ณผ๋ฅผ ์‚ฌ๋žŒ์ด ๋ณด๊ธฐ ์ข‹๊ฒŒ html, xml, csv ํ™•์žฅ์ž๋กœ Report๋ฅผ ์ƒ์„ฑํ•ด ์ฃผ๋Š” ์นœ๊ตฌ๋ž๋‹ˆ๋‹ค.

์ด ์นœ๊ตฌ๋Š” Test ๊ฒฐ๊ณผ๊ฐ€ ๊ฐœ๋ฐœ์ž๊ฐ€ ์„ค์ •ํ•œ coverage ๊ธฐ์ค€์„ ๋งŒ์กฑ์‹œํ‚ค์ง€ ๋ชปํ•˜๋ฉด ๋ฐฐํฌ๋ฅผ ๋ง‰์„ ์ˆ˜๋„ ์žˆ์–ด์š”. ์ด๋กœ ์ธํ•ด Branch Coverage๊ฐ€ 100%์ธ Project๋„ ์กด์žฌํ•œ๋‹ค๊ณ  ํ•˜๋„ค์š”.

์ด ์นœ๊ตฌ๋ฅผ ์š”์•ฝํ•˜์ž๋ฉด ์•„๋ž˜์™€ ๊ฐ™์•„์š”.

1. Line, Branch Coverage ์ œ๊ณต.

2. Code coverage ๊ฒฐ๊ณผ๋ฅผ File ํ˜•ํƒœ๋กœ ์ œ๊ณต.
   โˆ™ html, xml, csv

3. ๊ฐœ๋ฐœ์ž๊ฐ€ ์„ค์ •ํ•œ Coverage ๊ธฐ์ค€ ๋งŒ์กฑ ์—ฌ๋ถ€ ํ™•์ธ.




        ๐Ÿ“ฆ Coverage ๋ž€?

Code Coverage๋Š” Test Case๊ฐ€ ์–ผ๋งˆ๋‚˜ ์ถฉ์กฑ๋˜์—ˆ๋Š”์ง€๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ์ง€ํ‘œ ์ค‘ ํ•˜๋‚˜์—์š”. Test๋ฅผ ์ง„ํ–‰ํ–ˆ์„ ๋•Œ 'Code ์ž์ฒด๊ฐ€ ์–ผ๋งˆ๋‚˜ ์‹คํ–‰ ๋˜์—ˆ๋Š”๊ฐ€?'๋ฅผ ์ˆ˜์น˜๋กœ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด์—์š”.

Code Coverage๋Š” Source Code ๊ธฐ๋ฐ˜์œผ๋กœ ์ˆ˜ํ–‰ํ•˜๋Š” White Box Test๋ฅผ ํ†ตํ•ด ์ธก์ •ํ•˜๊ฒŒ ๋œ๋‹ต๋‹ˆ๋‹ค.








        ๐Ÿ“ฆ Coverage ์ธก์ • ๊ธฐ์ค€

Code ๊ตฌ์กฐ๋Š” ๊ตฌ๋ฌธ(Statement), ์กฐ๊ฑด(Condition), ๊ฒฐ์ •(Decision) ๊ตฌ์กฐ๋กœ ์ด๋ค„์ง€๊ณ , ์ด๋Ÿฌํ•œ Code์˜ ๊ตฌ์กฐ๋ฅผ ์–ผ๋งˆ๋‚˜ Cover ํ–ˆ๋Š๋ƒ์— ๋”ฐ๋ผ ์ธก์ • ๊ธฐ์ค€์ด ๋‚˜๋‰˜๊ฒŒ ๋˜์š”.


1. ๊ตฌ๋ฌธ (Statement)
   โˆ™ LineCoverage๋ผ๊ณ ๋„ ๋ถˆ๋ฆฌ๋ฉฐ,Code ํ•œ ์ค„์ด ํ•œ๋ฒˆ ์ด์ƒ ์‹คํ–‰๋œ๋‹ค๋ฉด ์ถฉ์กฑ. ๋งŒ์•ฝ x๊ฐ€ -1์ผ ๋•Œ, Test Data๋กœ ํ™œ์šฉํ•  ๊ฒฝ์šฐ 
      if๋ฌธ์˜ ์กฐ๊ฑด์„ ํ†ต๊ณผํ•˜์ง€ ๋ชปํ•˜๊ธฐ ๋•Œ๋ฌธ์— 3๋ฒˆ Code๋Š” ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค. ์ด ๋•Œ๋ฌธ์— ๊ตฌ๋ฌธ Coverage๋Š” 3/4 * 100 = 75 %

      ๊ฐ€ ๋œ๋‹ค.

public void jaCoCoCoverage() {
    system.out.prinln("Start Line");			// ์ฒซ๋ฒˆ์งธ ๊ตฌ๋ฌธ

    if ( x > 0) {								// ๋‘๋ฒˆ์งธ ๊ตฌ๋ฌธ
        system.out.println("middle Line");		// ์„ธ๋ฒˆ์งธ ๊ตฌ๋ฌธ
    }

    system.out.println("Last Line");			// ๋„ค๋ฒˆ์งธ ๊ตฌ๋ฌธ
}




2. ์กฐ๊ฑด (Condition)
   โˆ™ ์กฐ๊ฑด์€ ๋ชจ๋“  ์กฐ๊ฑด์‹์„ ๋งํ•˜๋ฉฐ, ๋‚ด๋ถ€ ์กฐ๊ฑด์ด true / false์˜ ๊ฒฝ์šฐ๋ฅผ ์ถฉ์กฑํ•˜๋Š”์ง€ ํ™•์ธ.

public void jaCoCoCoverage(int a, int b) {
	// Method A ์‹คํ–‰ - ํ•œ๋ฒˆ
    if ( a > 0 && b < 0) {				// ๋‘๋ฒˆ
    	// Method B ์‹คํ–‰ - ์„ธ๋ฒˆ
    }
    
    // Method C ์‹คํ–‰ - ๋„ค๋ฒˆ
}


์œ„์˜ Code์—์„œ ๋‚ด๋ถ€ ์กฐ๊ฑด์‹์€ a > 0์ผ ๋•Œ์™€ b < 0 ๋•Œ๋ฅผ ์ด์•ผ๊ธฐํ•˜๋ฉฐ, ๊ฐ๊ฐ Boolean Type์˜ Return ๊ฐ’์ด ๋‚˜์˜ค๋ฉด ์กฐ๊ฑด Coverage๋ฅผ ๋งŒ์กฑ.

a = 1, b = 1์ธ Test Case์™€ a = -1, b = -1 Test Case๋ฅผ ๋งŒ๋“ค๋ฉด ์กฐ๊ฑด์‹ a > 0, b < 0์€ ๊ฐ๊ฐ ์–‘์ˆ˜์ผ ๋•Œ์™€ ์Œ์ˆ˜์ผ ๋•Œ ๋ชจ๋‘ Test Case๊ฐ€ ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์กฐ๊ฑด Coverage๋ฅผ ๋งŒ์กฑ.

ํ•˜์ง€๋งŒ, a > 0 && b < 0์€ ๋ชจ๋‘ false๊ฐ€ ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์กฐ๊ฑด Coverage๋ฅผ ๋งŒ์กฑํ•˜๋”๋ผ๋„ ๊ตฌ๋ฌธ Coverage์™€ ๊ฒฐ์ • Coverage๋ฅผ ๋งŒ์กฑํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ ๋ฐœ์ƒ.



3. ๊ฒฐ์ • (Decision)

Branch Coverage๋ผ๊ณ ๋„ ๋ถˆ๋ฆฌ๋ฉฐ, ๋ชจ๋“  ์กฐ๊ฑด์‹์ด true / false๋ฅผ ๊ฐ€์ง€๊ฒŒ ๋˜๋ฉด ์ถฉ์กฑ.

public void jaCoCoCoverage(int a, int b) {
	// Method A ์‹คํ–‰ - ํ•œ๋ฒˆ
    if ( a > 0 && b < 0) {				// ๋‘๋ฒˆ
    	// Method B ์‹คํ–‰ - ์„ธ๋ฒˆ
    }
    
    // Method C ์‹คํ–‰ - ๋„ค๋ฒˆ
}


์œ„์˜ ํ•จ์ˆ˜์—์„œ ๊ฒฐ์ •์€ ๋‚ด๋ถ€ ์กฐ๊ฑด์ด ์•„๋‹Œ ์กฐ๊ฑด์‹์„ ๋งํ•œ๋‹ค. ์ฆ‰, ์œ„์— Code์—์„œ a > 0 && b < 0์„ ์ด์•ผ๊ธฐํ•˜๋Š” ๊ฒƒ.
๋”ฐ๋ผ์„œ a = 1, b = 1์ผ ๋•Œ์™€ a = -1, b = 3์œผ๋กœ Test Case ๊ตฌ์„ฑ ์‹œ ๊ฒฐ์ • Coverage ๋งŒ์กฑ.






        ๐Ÿ“ฆ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” Code Coverage

๊ฐ€์žฅ ๋งŽ์€ ์ด์šฉ์„ ํ•˜๋Š” Coverage ๋ฐฉ์‹์€ ๊ตฌ๋ฌธ Coverage์—์š”. ์™œ๋ƒํ•˜๋ฉด ์กฐ๊ฑด ์ปค๋ฒ„๋ฆฌ์ง€, ๋ธŒ๋žœ์น˜ ์ปค๋ฒ„๋ฆฌ์ง€์˜ ๊ฒฝ์šฐ ์ฝ”๋“œ ์‹คํ–‰์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋ณด๋‹ค Logic์˜ ์‹œ๋‚˜๋ฆฌ์˜ค์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ์— ๋” ๊ฐ€๊น๊ธฐ ๋•Œ๋ฌธ์ด์—์š”.

์œ„ ๋‘ ์ปค๋ฒ„๋ฆฌ์ง€๋Š” ์กฐ๊ฑด๋ฌธ์ด ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์ฝ”๋“œ์˜ ๊ฒฝ์šฐ ๊ทธ ์ฝ”๋“œ๋Š” ์ปค๋ฒ„๋ฆฌ์ง€ ๋Œ€์ƒ์—์„œ ์•„์˜ˆ ์ œ์™ธ๋ฅผ ์‹œ์ผœ๋ฒ„๋ ค์š”. ์ฆ‰, ํ•ด๋‹น ์ฝ”๋“œ๋“ค์„ ํ…Œ์ŠคํŠธํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด์ง€์š”.

๊ทธ๋ ‡์ง€๋งŒ ๊ตฌ๋ฌธ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ๋งŒ์กฑํ•œ๋‹ค๋ฉด ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์ปค๋ฒ„ํ–ˆ๋‹ค๊ณ  ๋งํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด์—์š”. ๋ฌผ๋ก  ์œ„์— ๊ฒฐ์ • ์ปค๋ฒ„๋ฆฌ์ง€์˜ ์ฝ”๋“œ ์˜ˆ์‹œ์—์„œ ์กฐ๊ฑด์‹์ด false์ธ ์‹œ๋‚˜๋ฆฌ์˜ค์— ๋Œ€ํ•ด ํ…Œ์ŠคํŠธ ๋๋‹ค๊ณ  ๋ณด์žฅํ•  ์ˆ˜๋Š” ์—†๊ฒ ์ง€๋งŒ, ๊ทธ๋ž˜๋„ ์กฐ๊ฑด๋ฌธ ๋‚ด๋ถ€์˜ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰ ๋˜์—ˆ์„ ๋•Œ ๋ฌธ์ œ๊ฐ€ ์—†๋‹ค๋Š” ๊ฒƒ์€ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด์—์š”.



 

    ๐Ÿ”ฝ  Spring Boot Gradle

        ๐Ÿ“ฆ JaCoCo Plugin ์ถ”๊ฐ€

Gradle ์„ค์ •์— JaCoCo Plugin์„ ์ถ”๊ฐ€ํ•˜๊ณ , Plugin ์„ค์ •์„ ํ•ด์ฃผ์–ด์•ผ ํ•ด์š”. reportsDir๋กœ Test ๊ฒฐ๊ณผ Report๋ฅผ ์ €์žฅํ•  ๊ฒฝ๋กœ๋ฅผ ๋ฐ”๊ฟ€์ˆ˜๋„ ์žˆ๋‹ต๋‹ˆ๋‹ค.


build.gradle

plugins {
    id 'jacoco'
}

jacoco {
  // JaCoCo ๋ฒ„์ „
  toolVersion = "0.8.5"

//  ํ…Œ์ŠคํŠธ๊ฒฐ๊ณผ ๋ฆฌํฌํŠธ๋ฅผ ์ €์žฅํ•  ๊ฒฝ๋กœ ๋ณ€๊ฒฝ
//  default๋Š” "${project.reporting.baseDir}/jacoco"
//  reportsDir = file("$buildDir/customJacocoReportDir")
}

 

build.gradle.kts

// kotlin DSL

plugins {
  jacoco
}

jacoco {
  // JaCoCo ๋ฒ„์ „
  toolVersion = "0.8.5"

//  ํ…Œ์ŠคํŠธ๊ฒฐ๊ณผ ๋ฆฌํฌํŠธ๋ฅผ ์ €์žฅํ•  ๊ฒฝ๋กœ ๋ณ€๊ฒฝ
//  default๋Š” "${project.reporting.baseDir}/jacoco"
//  reportsDir = file("$buildDir/customJacocoReportDir")
}


์œ„์™€ ๊ฐ™์ด Gradle ์„ค์ •์— JaCoCo Plugin์„ ์ถ”๊ฐ€ํ•˜๊ณ , Plugin ์„ค์ •์„ ํ•ด์ฃผ์—ˆ์–ด์š”. repotsDir๋กœ Test Report ์ €์žฅ ๊ฒฝ๋กœ๋ฅผ ๋ฐ”๊ฟ€ ์ˆ˜๋„ ์žˆ์–ด์š”.

์žํ”„๋ง(Java + Spring)์˜ build.gradle์— ์ž‘์„ฑํ•˜๋ฉด ๋˜๊ณ , ์ฝ”ํ”„๋ง(Kotlin + Spring)์˜ ๊ฒฝ์šฐ build.gradle.kts์— ์ž‘์„ฑํ•ด ์ฃผ๋ฉด ๋˜์š”.

build.gradle

 

 

    ๐Ÿ”ฝ  Gradle task Configuration

        ๐Ÿ“ฆ Test Report Save and Coverage Check

Jacoco Gradle Plugin์—๋Š” jacocoTestReport์™€ jacocoTestCoverageVerification task ๋‘ ๊ฐ€์ง€๊ฐ€ ์กด์žฌํ•˜๋Š” ๊ฒƒ์ด์—์š”.

1. jacoco TestReport : Binary Coverage ๊ฒฐ๊ณผ๋ฅผ ์‚ฌ๋žŒ์ด ์ฝ๊ธฐ ์ข‹์€ ํ˜•ํƒœ์˜ Report๋กœ ์ €์žฅ. html File๋กœ ์ƒ์„ฑํ•˜์—ฌ ์‚ฌ๋žŒ์ด ๋ˆˆ์œผ๋กœ ์‰ฝ๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ณ , SonarQube ๋“ฑ์œผ๋กœ ์—ฐ๋™ํ•˜๊ธฐ ์œ„ํ•ด xml, csv ๊ฐ™์€ ํ˜•ํƒœ๋กœ Report ์ƒ์„ฑ ๊ฐ€๋Šฅ.

2. jacocoTestCoverageVerification : ๊ฐœ๋ฐœ์ž๊ฐ€ ์›ํ•˜๋Š” Coverage ๊ธฐ์ค€์„ ๋งŒ์กฑํ•˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” task. ์˜ˆ๋ฅผ ๋“ค์–ด Branch Coverage๋ฅผ ์ตœ์†Œ 80% ์ด์ƒ์œผ๋กœ ์œ ์ง€ํ•˜๊ณ  ์‹ถ์„ ๋•Œ, ์ด ๊ณณ์— ์„ค์ •. test task์™€ ๊ฐ™์ด Gradle Build ์„ฑ๊ณต / ์‹คํŒจ๋ฅผ ๊ฒฐ๊ณผ๋กœ ๋ณด์—ฌ์คŒ.


build.gradle

jacocoTestReport {
  reports {
    // ์›ํ•˜๋Š” ๋ฆฌํฌํŠธ๋ฅผ ์ผœ๊ณ  ๋Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    html.enabled true
    xml.enabled false
    csv.enabled false

//  ๊ฐ ๋ฆฌํฌํŠธ ํƒ€์ž… ๋งˆ๋‹ค ๋ฆฌํฌํŠธ ์ €์žฅ ๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
//  html.destination file("$buildDir/jacocoHtml")
//  xml.destination file("$buildDir/jacoco.xml")
  }
}

jacocoTestCoverageVerification {
  violationRules {
    rule {
      element = 'CLASS'

      limit {
        counter = 'BRANCH'
        value = 'COVEREDRATIO'
        minimum = 0.90
      }
      
      excludes = []
    }
  }
}
๋ฐ˜์‘ํ˜•



build.gradle.kts

// kotlin DSL

tasks.jacocoTestReport {
  reports {
    // ์›ํ•˜๋Š” ๋ฆฌํฌํŠธ๋ฅผ ์ผœ๊ณ  ๋Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    html.isEnabled = true
    xml.isEnabled = false
    csv.isEnabled = false

//  ๊ฐ ๋ฆฌํฌํŠธ ํƒ€์ž… ๋งˆ๋‹ค ๋ฆฌํฌํŠธ ์ €์žฅ ๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
//  html.destination = file("$buildDir/jacocoHtml")
//  xml.destination = file("$buildDir/jacoco.xml")
  }
}

tasks.jacocoTestCoverageVerification {
  violationRules {
    rule {
      element = "CLASS"

      limit {
        counter = "BRANCH"
        value = "COVEREDRATIO"
        minimum = "0.90".toBigDecimal()
      }
      
      excludes = listOf("*.Kotlin.*")
    }
  }
}
728x90

 

build.gradle

 

1. element : Coverage๋ฅผ Checkํ•  ๋‹จ์œ„(๊ธฐ์ค€)์„ ์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด 6๊ฐœ ๊ธฐ์ค€ ์ œ๊ณต.
   โˆ™ BUNDLE : Package Bundle(Project์˜ ๋ชจ๋“  File - Default Value)
   โˆ™ CLASS
   โˆ™ GROUP : ๋…ผ๋ฆฌ์  Bundle Group
   โˆ™ METHOD
   โˆ™ PACKAGE
   โˆ™ SOURCEFILE


2. counter : limit Method๋ฅผ ํ†ตํ•ด ์ง€์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, Coverage ์ธก์ •์˜ ์ตœ์†Œ ๋‹จ์œ„. ์ด ๋•Œ ์ธก์ •์€ java byte code๊ฐ€ ์‹คํ–‰๋œ ๊ฒƒ์„ ๊ธฐ์ค€์œผ๋กœ ์ธก์ •๋˜๋ฉฐ, ์ด 6๊ฐœ ๋‹จ์œ„ ์ œ๊ณต.
   โˆ™ BRANCH : ์กฐ๊ฑด๋ฌธ ๋“ฑ์˜ ๋ถ„๊ธฐ๋ฌธ ์ˆ˜
   โˆ™ CLASS : Class ์ˆ˜, ๋‚ด๋ถ€ Method๊ฐ€ ํ•œ ๋ฒˆ์ด๋ผ๋„ ์‹คํ–‰๋œ๋‹ค๋ฉด ์‹คํ–‰์œผ๋กœ ๊ฐ„์ฃผ.
   โˆ™ COMPLEXITY : ๋ณต์žก๋„ 
   โˆ™ INSTRUCTION : JAVA Byte Code ๋ช…๋ น ์ˆ˜ (Default Value)
   โˆ™ METHOD : Method ๊ฐœ์ˆ˜, Method๊ฐ€ ํ•œ ๋ฒˆ์ด๋ผ๋„ ์‹คํ–‰๋œ๋‹ค๋ฉด ์‹คํ–‰์œผ๋กœ ๊ฐ„์ฃผ.
   โˆ™ LINE : ๋นˆ ์ค„์„ ์ œ์™ธํ•œ ์‹ค์ œ Code Line ์ˆ˜, Line์ด ํ•œ ๋ฒˆ์ด๋ผ๋„ ์‹คํ–‰๋˜๋ฉด ์‹คํ–‰์œผ๋กœ ๊ฐ„์ฃผ.


3. value : limit Method๋ฅผ ํ†ตํ•ด ์ง€์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ธก์ • Coverage๋ฅผ ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ๋ณด์—ฌ์ค„ ๊ฒƒ์ธ๊ฐ€์— ๋Œ€ํ•œ ์ •์˜. ์ด 5๊ฐœ ๋ฐฉ์‹ ์ œ๊ณต.
   โˆ™ CONVEREDCOUNT : Cover๋œ ๊ฐœ์ˆ˜
   โˆ™ COVEREDRATIO : Cover๋œ ๋น„์œจ, 0 ~ 1 ์‚ฌ์ด ์ˆซ์ž๋กœ 1์ด 100% (Default Value)
   โˆ™ MISSEDCOUNT : Cover๋˜์ง€ ์•Š์€ ๊ฐœ์ˆ˜
   โˆ™ MISSEDRATIO : Cover๋˜์ง€ ์•Š์€ ๋น„์œจ, 0 ~ 1 ์‚ฌ์ด ์ˆซ์ž๋กœ 1์ด 100%
   โˆ™ TOTALCOUNT : ์ „์ฒด ๊ฐœ์ˆ˜


4. minimum : counter ๊ฐ’์„ value์— ๋งž๊ฒŒ ํ‘œํ˜„ํ–ˆ์„ ๋•Œ์˜ ์ตœ์†Œ๊ฐ’. ์ด ๊ฐ’์„ ํ†ตํ•ด jacoco Test CoverageVerification ์„ฑ๊ณต ์—ฌ๋ถ€ ๊ฒฐ์ •. ํ•ด๋‹น ๊ฐ’์€ BigDecimal Type์ด๊ณ , ํ‘œ๊ธฐ ์ž๋ฆฟ์ˆ˜๋งŒํผ Value ์ถœ๋ ฅ. 0.80์ด ์•„๋‹ˆ๋ผ 0.8 ์ž…๋ ฅ ์‹œ Coverage๊ฐ€ 0.87์ด๋ผ๋„ 0.8๋กœ ํ‘œ๊ธฐ.


5. excludes : Coverage ์ธก์ • ์‹œ ์ œ์™ธํ•  Class๋ฅผ ์ง€์ •. Package Level์˜ ๊ฒฝ๋กœ๋กœ ์ง€์ •ํ•˜๋ฉฐ, *, ?๋ฅผ ์ด์šฉ.

 

JaCoCo Plugin์€ ์ž๋™์œผ๋กœ ๋ชจ๋“  Test Type์˜ task์— JaCoCoTaskExtension์„ ์ถ”๊ฐ€ํ•˜๊ณ , test task์—์„œ ๊ทธ ์„ค์ •์„ ๋ณ€๊ฒฝ(jaCoCo specific task configuration)ํ•  ์ˆ˜ ์žˆ์–ด์š”.


build.gradle

test {
  jacoco {
    destinationFile = file("$buildDir/jacoco/jacoco.exec")
  }
}


build.gradle.kts

tasks.test {
  extensions.configure(JacocoTaskExtension::class) {
    destinationFile = file("$buildDir/jacoco/jacoco.exec")
  }
}


์•„๋ž˜ Code๋Š” Plugin์—์„œ test task์— default๋กœ ์„ค์ •๋œ ๊ฐ’์ด์—์š”. ์ด ๊ฐ’๋“ค์€ ์œ„์˜ destinationFile์ฒ˜๋Ÿผ Overrideํ•  ์ˆ˜ ์žˆ์–ด์š”.


build.gradle

test {
    jacoco {
        enabled = true
        destinationFile = file("$buildDir/jacoco/${name}.exec")
        includes = []
        excludes = []
        excludeClassLoaders = []
        includeNoLocationClasses = false
        sessionId = "<auto-generated value>"
        dumpOnExit = true
        classDumpDir = null
        output = JacocoTaskExtension.Output.FILE
        address = "localhost"
        port = 6300
        jmx = false
    }
}



build.gradle.kts

tasks.getByName<Test>("test") {
    extensions.configure(JacocoTaskExtension::class) {
        isEnabled = true
        destinationFile = file("$buildDir/jacoco/$name.exec")
        includes = listOf()
        excludes = listOf()
        excludeClassLoaders = listOf()
        isIncludeNoLocationClasses = false
        sessionId = "<auto-generated value>"
        isDumpOnExit = true
        classDumpDir = null
        output = JacocoTaskExtension.Output.FILE
        address = "localhost"
        port = 6300
        isJmx = false
    }
}

 

build.gradle






 

    ๐Ÿ”ฝ  JaCoCo ์‚ฌ์šฉํ•ด ๋ณด๊ธฐ

        ๐Ÿ“ฆ Code ์ค€๋น„์™€ Test ์‹คํ–‰

JaCoCo Test๋ฅผ ๋Œ๋ ค ๋ณผ Source Code์™€ Test Code๋ฅผ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค. ์ฃผ๋‹ˆํ•˜๋ž‘์€ JUnit5๋กœ Test๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์žˆ์–ด์š”.
๋กœ ์ธํ•ด JUnit์ด ํ•จ๊ป˜ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ์–ด์•ผ ํ•ด์š”.

build.gradle

test {
  useJUnitPlatform()
  finalizedBy 'jacocoTestReport' // ์ถ”๊ฐ€
}


build.gradle.kts

tasks.test {
    extensions.configure(JacocoTaskExtension::class) {
        destinationFile = file("$buildDir/jacoco/jacoco.exec")
    }

    finalizedBy("jacocoTestReport")
}


JaCoCo Plugin์„ ํ†ตํ•ด jacocoTestReport Task๋กœ Report๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์ „์— test Task๊ฐ€ ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๊ณ , jacoco Test Report Task์—๋Š” test Task์™€์˜ ์˜์กด์„ฑ์ด ์„ค์ •๋˜์–ด ์žˆ์ง€ ์•Š์•„์š”.

๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— test Task์— ์˜์กด์„ฑ์— ๋Œ€ํ•œ ์„ค์ •์„ ํ•ด์ฃผ์–ด์•ผ ํ•ด์š”.

jacocoTestReport Task์™€ jacocoTestCoverageVerification Task ์ˆœ์„œ๋„ ๋งค์šฐ ์ค‘์š”ํ•ด์š”.

์š”์•ฝํ•˜์ž๋ฉด Unit Test๊ฐ€ ์ง„ํ–‰๋˜๊ณ , jacoco Test Report๊ฐ€ ๋™์ž‘ํ•œ ๋’ค jacoco Test Coverage Verification ์ˆœ์„œ๋กœ task๊ฐ€ ์ง„ํ–‰๋˜์–ด์•ผ ํ•ด์š”.

์ด ์ˆœ์„œ๋Š” ์œ„์˜ Code์™€ ๊ฐ™์ด finalizedBy๋ผ๋Š” Method๋ฅผ ์ด์šฉํ•˜์—ฌ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์–ด์š”.

๋งŒ์•ฝ ์œ„์˜ ์„ค์ •์„ ํ•˜์ง€ ์•Š์œผ๋ฉด jacocoTestReport task์™€ jacocoTestCoverageVerification task๋ฅผ Skipํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, ์ด๋ ‡๊ฒŒ ๋˜๋ฉด ์‹ค์ œ Test ์ž์ฒด๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š๊ฒŒ ๋˜์š”.

์„ค์ •์ด ๋‹ค ๋˜์—ˆ์œผ๋‹ˆ Test๋ฅผ ๋Œ๋ ค ๋ณผ๊ฒŒ์š”. ์•„๋ž˜ ๋ช…๋ น์–ด๋กœ๋„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ณ ,

./gradlew test


InteliJ ์˜ค๋ฅธ์ชฝ Gradle -> Tasks -> Verification -> Test๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜๋„ ์žˆ์–ด์š”.



Coverage ํ†ต๊ณผ ์‹œ build/reports/jacoco/test/html ๊ฒฝ๋กœ์— index.html File์ด ์ƒ์„ฑ๋œ๋‹ต๋‹ˆ๋‹ค.


Index.html์„ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ํ†ตํ•ด ์—ด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด Corverage๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์š”.


JaCoCo Index.html








    ๐Ÿ”ฝ  ์ž… ๋ง›์— ๋งž์ถ”๊ธฐ

        ๐Ÿ“ฆ Coverage ์ œ์™ธ

JaCoCo๋กœ Test Code ์ˆ˜ํ–‰ ์‹œ Test ํ•  ํ•„์š”๊ฐ€ ์—†๋Š” ๋ถ€๋ถ„๋“ค๋„ Coverage์— ์žกํž๋•Œ, ์ด๋ฅผ ์ œ์™ธํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ณผ๊ฒŒ์š”.

1. ์ œ์™ธํ•  Class excludes ์ถ”๊ฐ€
   โˆ™ @SpringBootTest๊ฐ€ ๋ถ™์€ Class
   โˆ™ ๊ฐ ์ข… Config Class
   โˆ™ DTO Class
   โˆ™ Request / Response Class
   โˆ™ Interceprot
   โˆ™ Exception
   โˆ™ QueryDSL ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“ค์–ด์ง„ Q Domain Class

์œ„์— ์žˆ๋Š” ์นœ๊ตฌ๋“ค ์™ธ์— ์ œ์™ธ๊ฐ€ ํ•„์š”ํ•œ ๊ฒƒ๋“ค๋„ ์ถ”๊ฐ€ํ•ด ์ฃผ๋ฉด ๋˜์š”. Q Class ๊ฒฝ์šฐ Class ์ด๋ฆ„์ด Q + ๋Œ€๋ฌธ์ž๋กœ ์‹œ์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ทธ ํŠน์ง•์„ ์žก์•„ ์ œ์™ธ Class๋กœ ์ถ”๊ฐ€ํ•ด์ฃผ์–ด์•ผ ํ•ด์š”.

jacocoTestReport์—๋Š” ๋ถ„์„ Report ์ƒ์„ฑ ์‹œ ํŠน์ • File ์ œ์™ธ๋ฅผ ์œ„ํ•ด defQdomain = [] ๋ถ€ํ„ฐ afterEvaluate ์ชฝ์„ ์ถ”๊ฐ€ํ•ด ์ค„ ๊ฒƒ์ด์—์š”.

jacocoTestCoverageVerification์—๋Š” ๊ฒ€์ฆ์„ ์ œ์™ธํ•  Class๋“ค์„ excludes์— ์ถ”๊ฐ€ํ•ด ์ค„ ๊ฒƒ์ด์—์š”. ์—ฌ๊ธฐ์„œ๋„ Q Class๋“ค์„ ํฌํ•จํ•ด์„œ ์ œ์™ธํ•  ๊ฒƒ์ด๊ตฌ์š”.

 
build.gradle

jacocoTestReport {
    dependsOn test
    reports {
        html.enabled true
        xml.enabled true
        csv.enabled true

        html.destination file("src/jacoco/jacoco.html")
        xml.destination file("src/jacoco/jacoco.xml")
        csv.destination file("src/jacoco/jacoco.csv")
    }

    def Qdomains = []
    for (qPattern in '**/QA'..'**/QZ') {
        Qdomains.add(qPattern + '*')
    }

    afterEvaluate {
        classDirectories.setFrom(
                files(classDirectories.files.collect {
                    fileTree(dir: it, excludes: [
                    "**/*Application*",
                    "**/*Config*",
                    "**/*Dto*",
                    "**/*Request*",
                    "**/*Response*",
                    "**/*Interceptor*",
                    "**/*Exception*"
                    ] + Qdomains)
                })
        )
    }
    finalizedBy 'jacocoTestCoverageVerification'
}

jacocoTestCoverageVerification {
    def Qdomains = []
    for (qPattern in '*.QA'..'*.QZ') {
        Qdomains.add(qPattern + '*')
    }

    violationRules {
        rule {
            element = 'CLASS'
            enabled = true

            limit {
                counter = 'LINE'
                value = 'COVEREDRATIO'
                minimum = 0.60
            }

            limit {
                counter = 'BRANCH'
                value = 'COVEREDRATIO'
                minimum = 0.60
            }

            excludes = [
                    "**.*Application*",
                    "**.*Config*",
                    "**.*Dto*",
                    "**.*Request*",
                    "**.*Response*",
                    "**.*Interceptor*",
                    "**.*Exception*"
            ] + Qdomains
        }
    }
}



์ถ”๊ฐ€์ ์œผ๋กœ Lombok์„ ์‚ฌ์šฉํ•  ๋•Œ, getter, builder Method ๋“ฑ์„ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ์ด๋ฅผ Test ๋ฒ”์œ„์—์„œ ์ œ์™ธํ•  ํ•„์š”๋ฅผ ๋Š๊ปด ์ œ์™ธํ•ด ๋ณผ๊ฒŒ์š”.

์ตœ์ดˆ Project Root Directory ์•„๋ž˜ lombok.config File์„ ๋งŒ๋“ค๊ณ , ์•„๋ž˜ Option์„ ์ถ”๊ฐ€ํ•ด ์ฃผ์—ˆ์–ด์š”.
์ฐธ๊ณ ๋กœ resources Directroy ์•„๋ž˜ ์ƒ์„ฑ ์‹œ ์ ์šฉ์ด ์•ˆ๋˜๋‹ˆ ์ฃผ์˜ํ•ด์•ผ ํ•ด์š”.


lombok.config

 


์œ„ File์„ ์ž‘์„ฑํ•˜๊ณ , Gradle์„ ๋‹ค์‹œ ์‹คํ–‰ํ•ด์ฃผ์–ด์•ผ ํ•ด์š”.



JaCoCo Index.html



 

 

๐Ÿง ์ฐธ๊ณ  ์ž๋ฃŒ

Gradle ํ”„๋กœ์ ํŠธ์— JaCoCo ์„ค์ •ํ•˜๊ธฐ

ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€(Test Coverage)

 

์ฝ”๋“œ ๋ถ„์„ ๋„๊ตฌ ์ ์šฉ๊ธฐ - 1ํŽธ, ์ฝ”๋“œ ์ปค๋ฒ„๋ฆฌ์ง€(Code Coverage)๊ฐ€ ๋ญ”๊ฐ€์š”?

์•ˆ๋…•ํ•˜์„ธ์š”. ์šฐ์•„ํ•œํ…Œํฌ์ฝ”์Šค…

seller-lee.github.io

 

 

์ด์ „ ๊ธ€: [CI/CD] SonarQube๋ฅผ ํ†ตํ•ด Code Convention ์ ์šฉ


๋‹ค์Œ ๊ธ€:
[DevOps] JAVA Gradle JaCoCo (Code coverage) ์„ค์ •ํ•˜๊ธฐ (์ถ”๊ฐ€)

 

 

 

 

 

 

728x90
๋ฐ˜์‘ํ˜•