๐ŸŒฑ ๋ฌธ์ œ ์ƒํ™ฉ

2024.07.24 - [Back-end] - [Spring Boot] ๋ฉ€ํ‹ฐ๋ชจ๋“ˆ์— JaCoCo + JaCoCo Report Aggregation ์ ์šฉํ•˜๊ธฐ

 

[Spring Boot] ๋ฉ€ํ‹ฐ๋ชจ๋“ˆ์— JaCoCo + JaCoCo Report Aggregation ์ ์šฉํ•˜๊ธฐ

macOSIntelliJ IDEAJava 17Spring Boot 3.xGroovy๐Ÿ“„ JaCoCoJaCoCo ํ”Œ๋Ÿฌ๊ทธ์ธ ์ ์šฉJaCoCo :: Maven Plugin์—์„œ JaCoCo์˜ ์ตœ์‹  ๋ฒ„์ „์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.์ฒ˜์Œ์—๋Š” build.gradle์— JaCoCo ํƒœ์Šคํฌ๋ฅผ ์„ค์ •ํ–ˆ์œผ๋‚˜, ๋„ˆ๋ฌด ๋ณต์žกํ•ด์ ธ์„œ ๋ณ„๋„

chaewsscode.tistory.com

์ด์ „์— ๋ฉ€ํ‹ฐ๋ชจ๋“ˆ + JaCoCo Report Aggregation์„ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ํ™•์ธํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ ์šฉํ•ด ๋ณด์•˜๋‹ค.

 

๊ฐ ๋ชจ๋“ˆ์—์„œ์˜ ๊ฒฐ๊ณผ ๋ณด๊ณ ์„œ๋ฅผ ์ง‘๊ณ„ํ•œ ์ข…ํ•ฉ ๋ณด๊ณ ์„œ๋ฅผ ์™„์„ฑํ•˜๋Š” ๋ฐ๋Š” ์„ฑ๊ณตํ–ˆ์ง€๋งŒ ์ง๋ฉดํ•˜๊ฒŒ ๋œ ๋˜ ๋‹ค๋ฅธ ๋ฌธ์ œ์ ์ด ์žˆ์—ˆ๋‹ค.

๊ฐ„๋‹จํ•˜๊ฒŒ ์„ค๋ช…ํ•˜์ž๋ฉด ๋‚ด ํ”„๋กœ์ ํŠธ์—๋Š” app, admin, domain ๋ชจ๋“ˆ์ด ์žˆ๋Š”๋ฐ ๋„๋ฉ”์ธ ๋ชจ๋“ˆ์˜ ๋ฉ”์„œ๋“œ ํ…Œ์ŠคํŠธ๊ฐ€ ๋‹ค๋ฅธ ๋ชจ๋“ˆ(app ๋˜๋Š” admin ๋ชจ๋“ˆ)์—์„œ ์‹คํ–‰๋˜์–ด ๋„๋ฉ”์ธ ๋ชจ๋“ˆ ์ž์ฒด์˜ ์ปค๋ฒ„๋ฆฌ์ง€๊ฐ€ ๋‚ฎ๊ฒŒ ๋‚˜์˜ค๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

 

JaCoCo๋Š” ๊ฐ ๋ชจ๋“ˆ๋ณ„๋กœ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ๊ณ„์‚ฐํ•˜๋Š”๋ฐ, ๋„๋ฉ”์ธ ๋ชจ๋“ˆ์˜ ์ฝ”๋“œ๊ฐ€ ๋‹ค๋ฅธ ๋ชจ๋“ˆ์˜ ํ…Œ์ŠคํŠธ์—์„œ๋งŒ ์‹คํ–‰๋˜์–ด JacocoTestCoverageVerification ๋‹จ๊ณ„์—์„œ ๋„๋ฉ”์ธ ๋ชจ๋“ˆ์˜ ์ปค๋ฒ„๋ฆฌ์ง€ ๊ธฐ์ค€์„ ์ถฉ์กฑํ•˜์ง€ ๋ชปํ•ด ๋นŒ๋“œ๊ฐ€ ์‹คํŒจํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

Rule violated for class Bid: lines covered ratio is 0.13, but expected minimum is 0.80
Rule violated for class User: lines covered ratio is 0.36, but expected minimum is 0.80
...

์™ผ์ชฝ์€ ๋„๋ฉ”์ธ ๋ชจ๋“ˆ์˜ ๋ณด๊ณ ์„œ, ์˜ค๋ฅธ์ชฝ์€ ์ „์ฒด ๋ชจ๋“ˆ ์ข…ํ•ฉ ๋ณด๊ณ ์„œ์ด๋‹ค. ์ด์ฒ˜๋Ÿผ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ํ…Œ์ŠคํŠธํ–ˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , ๋„๋ฉ”์ธ ๋ชจ๋“ˆ์˜ ์ปค๋ฒ„๋ฆฌ์ง€๊ฐ€ ๋‚ฎ๊ฒŒ ๋‚˜์˜ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๐ŸŒฑ ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

๋‚ด๊ฐ€ ์ƒ๊ฐํ•ด ๋‚ธ ๋ฐฉ๋ฒ•์€ ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ์—ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์€ ์–ด์ฉ” ์ˆ˜ ์—†์ด ๋„๋ฉ”์ธ ๋ชจ๋“ˆ ์ž์ฒด์—์„œ ๋ฉ”์„œ๋“œ๋“ค์„ ์ง์ ‘ ํ…Œ์ŠคํŠธํ•˜๋Š” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ.. ๋‘ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์€ JaCoCo์˜ ์„ค์ •์„ ์กฐ์ •ํ•ด ๋‹ค๋ฅธ ๋ชจ๋“ˆ์˜ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ๊ฒฐ๊ณผ๋„ ๋„๋ฉ”์ธ ๋ชจ๋“ˆ์˜ ์ปค๋ฒ„๋ฆฌ์ง€์— ํฌํ•จ๋˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด์—ˆ๋‹ค.

 

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

 

subprojects {
    apply plugin: 'java'
    apply plugin: 'jacoco'

    jacoco {
        toolVersion = '0.8.12'
    }

    test {
        finalizedBy 'jacocoTestReport'
    }

    jacocoTestReport {
        ...
    }

    jacocoTestCoverageVerification {
        enabled = false	// JaCoCo ๊ฒ€์ฆ ๋น„ํ™œ์„ฑํ™”
    }
}

์šฐ์„  ๊ฐ ๋ชจ๋“ˆ์—์„œ jacocoTestCoverageVerification ํƒœ์Šคํฌ๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•œ๋‹ค.

 

project(':support:jacoco') {
    apply plugin: 'jacoco-report-aggregation'
    
    testCodeCoverageReport {
        ...
    }
    
    ...
    
    task jacocoRootCoverageVerification(type: JacocoCoverageVerification) {
        dependsOn testCodeCoverageReport

        // ์ปค๋ฒ„๋ฆฌ์ง€ ์ธก์ •ํ•  ์†Œ์Šค ๋””๋ ‰ํ† ๋ฆฌ ์ง€์ •
        // ๋ชจ๋“  ์„œ๋ธŒ ํ”„๋กœ์ ํŠธ์˜ ์†Œ์Šค ๋””๋ ‰ํ† ๋ฆฌ ํฌํ•จ
        sourceDirectories.setFrom(files(allProjects
                .collect { it.sourceSets.main.allSource.srcDirs }))
        
        // ์†Œ์Šค ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด ํด๋ž˜์Šค๋ฅผ ์ปดํŒŒ์ผ ํ•œ ๊ฒฐ๊ณผ์ธ *.class ํŒŒ์ผ์ด ์žˆ๋Š” ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ง€์ •
        // ๊ฐ ์„œ๋ธŒ ํ”„๋กœ์ ํŠธ์˜ ๋นŒ๋“œ ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์˜ ํด๋ž˜์Šค ํŒŒ์ผ
        classDirectories.setFrom(files(allProjects
                .collect { it.fileTree(dir: "${it.buildDir}/classes/java/main", exclude:
                        Qdomains + excludeFromCoverage.stream()
                                .map(s -> s + ".class")
                                .collect(Collectors.toList())) }))
                                
        // ๋ชจ๋“  ์„œ๋ธŒ ํ”„๋กœ์ ํŠธ์—์„œ ์ƒ์„ฑ๋œ JaCoCo ์‹คํ–‰ ๋ฐ์ดํ„ฐ ํฌํ•จ
        executionData.setFrom(files(allProjects.collect { it.jacocoTestReport.executionData }))

        violationRules {
            rule {
                enabled = true
                element = "CLASS"
                limit {
                    counter = "LINE"
                    value = "COVEREDRATIO"
                    minimum = 0.80
                }
                excludes += Qdomains.stream()
                        .map(s -> s.replace("/", "."))
                        .collect(Collectors.toList()) +
                        excludeFromCoverage.stream()
                        .map(s -> s.replace("/", "."))
                        .collect(Collectors.toList())
            }
        }
    }

    check.dependsOn jacocoRootCoverageVerification
}

 

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