ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JaCoCo(Java Code Coverage) 적용하기
    공부일기 2021. 8. 7. 21:02

    JaCoCo란?

    JaCoCo is a free code coverage library for Java, which has been created by the EclEmma team based on the lessons learned from using and integration existing libraries for many years - JaCoCo

    Jacoco (Java Code Coverage) 자바 코드 커버리지를 체크하는 데 사용되는 오픈소스 라이브러리입니다.

    다음과 같은 특징 덕에 코드 커버리지를 쉽게 확인하고, 관리할 수 있도록 도와줍니다.

    • Jacoco의 특징
      • Line, Branch Coverage를 제공한다.
      • 코드 커버리지 결과를 파일 형태로 저장할 수 있다.
        • html, xml, csv 등으로 Report를 생성합니다.
      • 설정한 커버리지 기준을 만족하는지 확인할 수 있다.
        • 설정한 커버리지 기준을 만족하지 못할 경우 빌드하지 못하도록 막을 수 있습니다.

    JaCoCo 프로젝트에 적용하기

    JaCoCo plugin 설정

    먼저 build.gradle 파일 plugins 블록에 id 'jacoco'를 추가합니다.

    plugins { 
        id 'jacoco' 
    }

    jacoco 위의 설정들은 개인이 진행중인 프로젝트에 맞게 설정합니다.

    지금과 같이 자바 플러그인과 같이 프로젝트에 적용되는 경우, jacocoTestReport가 기본적으로 생성되고, HTML 리포트와 XML 리포트 파일이 $buildDir/reports/jacoco/test 경로에 자동으로 생성됩니다.

    gradle → task —> verification 항목에 추가된 것을 확인할 수 있습니다.

    생성된 두 개의 task는 반드시 test가 먼저 실행된 후에 실행이 되어야 합니다.

     

     

    Test와 JaCoCo의 의존 설정

    테스트와 코드 커버리지 보고서의 의존성을 설정하기 위해 build.gradle test 블록을 아래와 같이 설정합니다.

    test {
            ...
        useJUnitPlatform()
    
        finalizedBy 'jacocoTestReport' // 테스트가 먼저 실행되고 리포트가 작동된다.
    }

    JaCoCo plugin 설정 후 jacoco 로 이름이 붙은 JacocoPluginExtension 타입의 project extentions을 사용해 빌드 파일에서 사용 될 기본적인 설정을 합니다.

    jacoco {
        toolVersion = '0.8.7'
    }

     

     

    JaCoCoTestReports 설정

    테스트 결과를 받는 부분을 설정하는 부분입니다. html, scv, xml 등 다양한 형식으로 커버리지 측정 결과를 만들 수 있습니다. 기본은 html, 추후 Sonar Qube와의 연동을 위해 csv 형식도 true로 설정합니다.

    jacocoTestReport {
        dependsOn test // 리포트가 만들어지기 전 테스트 실행되어야 한다.
        reports {
            html.enabled true
            csv.enabled true
            xml.enabled false
        }
            ...
    }

     

     

    JaCoCoTestCoverageVerification 설정

    커버리지 검증 수준을 정의하는 부분입니다. 해당 부분에서 위반 규칙 등을 설정해 JaCoCo의 report를 검사하고, 설정한 최소 수준을 달성하지 못하면 task는 실패하게 됩니다.

    jacocoTestCoverageVerification { // 코드 커버리지 측정항목 시행
        def Qdomains = []
    
        for (qPattern in "*.QA".."*.QZ") {
            Qdomains.add(qPattern + "*")
        }
    
        violationRules { // 위반 규칙
            rule {
                limit {
                    counter = 'METHOD'
                    value = 'COVEREDRATIO'
                    minimum = 0.85
                }
    
                excludes = [
                        // 커버리지 제외할 클래스
                        '*.DataLoader*',
                        '*.CviApplication*',
                        '*.DummyData*',
                        '*.KakaoProfile*',
                        '*.NaverProfile*'
                ] + Qdomains
            }
        }
    }

    • 위반 규칙: 선언된 규칙을 위반하면 빌드가 자동으로 실패합니다.
    • rule : 적용하지 않은 부분은 회색 글씨로 표현했습니다.
      • enable: 해당 rule의 활성화 여부를 boolean으로 나타냄. default는 true
      • element: 커버리지 측정의 큰 단위. default는 BUNDLE
        • PACKAGE, CLASS, SOURCEFILE, METHOD
      • counter: 커버리지 측정의 최소 단위. default는 INSTRUCTION
        • CLASS, METHOD, LINE, BRACH 등의 값이 있습니다.
      • value: 측정한 counter 정보를 어떤 방식으로 보여줄지 정합니다.
        • TOTALCOUNT, COVEREDCOUNT, COVEREDRATIO 등의 값이 있습니다.
      • minimum: count 값을 value에 맞게 표현했을 때 최소 백분율. 이 값으로 jacoco coverage verification의 성공여부를 판단합니다.
      • excludes: 커버리지 검증에서 제외할 클래스를 지정합니다. 패키지 레벨의 경로를 지정합니다.
        • Qdomains 같은 경우 QueryDSL을 사용하고 있기 때문에 테스트할 필요가 없지만 '\*.Q\*' 와 같이 지정할 경우 추후 발생할 Question과 같은 클래스도 제외시킬 수 있어 jacocoTestCoverageVerification 블록 처음 부분에 Q domain 패턴의 경로를 모아서 추가했습니다.
      • include: 해당하는 rule을 적용할 대상을 package 수준으로 정의합니다. default는 전체 package

     

     

     

    JaCoCoReport에서 수집되지 않도록 제외하기

    JaCoCo의 테스트 검증 대상에서 벗어나도록 excludes 설정은 했지만, report 대상에는 그대로 남아있기 때문에 보고서엔 프로젝트 커버리지의 비율을 떨어뜨리는 모습으로 존재하고 있음을 확인할 수 있습니다.

     

    해결을 위해 jacocoTestRepost 블럭에서 설정한 디렉토리는 report 결과에 포함하지 않도록 합니다.

    jacocoTestReport {
        dependsOn test // 리포트가 만들어지기 전 테스트 실행되어야 한다.
        reports {
            html.enabled true
            csv.enabled true
            xml.enabled false
        }
    
        def Qdomains = []
        for(qPattern in "**/QA" .. "**/QZ") {
            Qdomains.add(qPattern+"*")
        }
    
        afterEvaluate {
            classDirectories.setFrom(files(classDirectories.files.collect {
                fileTree(dir: it, // 리포트 결과에 제외시킬 디렉토리
                        exclude: ['**/DataLoader*',
                                  '**/CviApplication*',
                                  '**/DummyData*',
                                  '**/KakaoProfile*',
                                  '**/NaverProfile*'] + Qdomains)
            }))
        }
    
        finalizedBy 'jacocoTestCoverageVerification'
    }

     

     

    주의할 점은 여기선 패키지+클래스 경로가 아닌 디렉토리 경로를 잡아줍니다.

     

     

    설정을 마치고 다시 리포트를 확인해보면 excludes 해준 클래스들이 리포트 결과에서 제외된 것을 확인할 수 있습니다.

     

     

    그러나 한 가지 문제가 아직 남아있습니다.

    현재 프로젝트에서 Lombok을 사용중이기 때문에 getter, builder와 같은 메서드가 존재합니다.

     

     

    getter와 builder와 같은 메서드 등을 커버리지 영역에 포함시키고 싶지 않은 경우 lombok.config 파일을 만들어 아래와 같이 설정해줍니다.

    lombok.addLombokGeneratedAnnotation = true

     

     

    다시 빌드하고 리포트를 확인해보면 롬복을 통해 만들어진 getter, builder 메서드는 사라진 것을 확인할 수 있습니다.

     

     

     

    testCoverage Task 만들기

    매번 jacocoTestReport, jacocoTestCoverageVerification task를 지정해주면 귀찮으니 이 task를 하나로 묶는 testCoverage라는 task를 만들 수 있습니다.

    task testCoverage(type: Test) {
        group 'verification'
        description 'Runs the unit tests with coverage'
    
        dependsOn(':test',
                ':jacocoTestReport',
                ':jacocoTestCoverageVerification')
    
        tasks['jacocoTestReport'].mustRunAfter(tasks['test'])
        tasks['jacocoTestCoverageVerification'].mustRunAfter(tasks['jacocoTestReport'])
    }

     

    test, jacocoTestReport, jacocoTestCoverageVerification 실행 후 testCoverage task를 실행하도록 dependsOn으로 설정합니다.

     

    실행 순서를 고정 시키기 위해 mustRunAfter로 순서를 지정합니다.

    밑의 두 줄을 확인해보면 다음과 같은 순서로 task가 실행됨을 확인할 수 있습니다.

     

    test → jacocoTestReport → jacocoTestCoverageVerification

    터미널에서 --console verbose 옵션을 추가해 실행순서를 확인하면 설정한 순서대로 실행됨을 확인할 수 있습니다.

     

    만약 최소 테스트 커버리지 비율을 95로 설정하고 빌드할 경우 다음과 같이 실패하는 것도 확인할 수 있습니다.

     

     

     

    참고:

    https://docs.gradle.org/current/userguide/jacoco_plugin.html

    https://techblog.woowahan.com/2661/

    https://bottom-to-top.tistory.com/36

     

    '공부일기' 카테고리의 다른 글

    JaCoCo와 SonarQube 연동하기  (0) 2021.08.11
    NAVER LOGIN API 연동  (0) 2021.07.19
    테스트 주도 개발 / 켄트 백  (1) 2021.02.14
Designed by Tistory.