본문 바로가기
Java/트러블 슈팅

테스트 커버리지 확인을 위한 jacoco 설정

by oneny 2023. 11. 23.

Jacoco

JaCoCo(Java Code Coverage)는 Java 언어를 대상으로 하는 코드 커버리지 도구이다. 이 도구는 소스코드의 얼마나 많은 부분이 실행되었는지 측정하여 코드 커버리지를 제공한다. 코드 커버리지는 테스트 스위트 또는 특정 테스트 케이스가 소스코드의 어느 정도를 실행했는지를 나타낸다. JaCoCo는 Java 애플리케이션의 클래스, 메서드, 라인 등의 다양한 수준에서 코드 커버리지를 측정할 수 있다. JaCoCo는 다음과 같은 주요 기능을 제공한다.

  • 라인 커버리지(Line Coverage): 소스 코드의 각 라인이 얼마나 실행되었는지 측정
  • 브랜치 커버리지(Branch Coverage): 조건 분기의 각 부분이 얼마나 실행되었는지 측정
  • 메서드 커버리지(Method Coverage): 각 메서드가 얼마나 실행되었는지 측정
  • 클래스 커버리지(Class Coverage): 각 클래스가 얼마나 실행되었는지 측정

JaCoCo는 Ant, Maven, Gradle 등에서 지원하기 때문에 JaCoCo 리포트를 통해 코드 커버리지에 대한 시각적인 피드백을 얻을 수 있고, 이를 통해 코드의 품질을 향상시키는 데 도움이 된다.

 

jacoco 플러그인 추가

plugins {
	id 'jacoco'
}

jacoco {
    toolVersion = "0.8.9" // jacoco 버전
    // reportsDirectory = layout.buildDirectory.dir("reports/jacoco")
}

프로젝트에 Java 플러그인이 적용되면, JacocoPluginExtension이라는 유형의 프로젝트 확장(extension)을 추가하여 이를 통해 JaCoCo를 사용하는데 필요한 기본값들을 구성할 수 있다. 기본 설정으로는 jacocoTestReport 작업이 실행될 때 HTML 보고서가 layout.buildDirectory.dir("reports/jacoco") 경로에 생성된다. 

 

코드 커버리지 보고서 생성과 테스트 실행 간의 의존성 정의

tasks.named('test') {
    finalizedBy jacocoTestReport
}

jacocoTestReport {
    dependsOn test
}
  • 'test' 작업 후에 항상 jacocoTestReport 작업 실행: 이 부분은 'test' 작업에 대해 설정을 변경한다. finalizedBy를 사용하여 test 작업이 실행된 후에 항상 jacocoTestReport 작업이 실행되도록 설정한다.
  • jacocoTestReport 작업은 'test' 작업에 의존: 이 부분은 jacocoTestReport 작업이 실행되기 전에 test 작업이 선행되어야 함을 나타낸다.

이 설정은 코드 커버리지 보고서를 생성하기 전에 항상 테스트가 실행되도록 보장한다.

 

Gradle task 설정 - 테스트 리포트 저장과 커버리지 체크

 

JaCoCo Gradle 플러그인에는 jacocoTestReportjacocoTestCoverageVerification task가 있다.

  • jacocoTestReport: 바이너리 커버리지 결과를 사람이 읽기 좋은 형태의 리포트로 저장한다. HTML 파일로 생성해 사람이 눈으로 확인할 수도 있고, SonarQube 등으로 연동하기 위해 XML, CSV 같은 형태로도 리포트를 생성할 수 있다.
  • jacocoTestCoverageVerification: 내가 원하는 커버리지 기준을 만족하는지 확인해 주는 task다. 예를 들어, 브랜치 커버리지를 최소한 80% 이상으로 유지하고 싶다면, 이 task에 설정하면 된다. test task처럼 Gradle 빌드의 성공/실패로 결과를 보여준다.

 

jacocoTestReport

jacocoTestReport {
    dependsOn test

    reports {
        xml.required = false
        csv.required = false

        html.outputLocation = layout.buildDirectory.dir('jacocoHtml')
    }

    afterEvaluate {
        getClassDirectories().setFrom(files(classDirectories.files.collect {
            fileTree(dir: it, exclude: [
                    "com/flab/idolu/global/filter/*"
            ])
        }))
    }
}

 

JacocoTestReport task는 위 설정처럼 다양한 형식으로 생성하고 원하는대로 구성할 수 있다. html.outputLocation 속성을 사용해서 HTML 보고서의 출력 디렉터리를 지정할 수 있다. layout.buildDirectory.dir('jacocoHtml')는 보고서를 저장할 디렉터리를 나타내며 아래 결과처럼 보고서의 출력 위치에 디렉터리가 생긴 것을 확인할 수 있다.

 

 

afterEvalue { ... } 부분을 보면 코드 커버리지 리포트를 생성할 때 클래스 디렉터리를 탐색하고 지정된 패턴과 일치하는 파일을 제외하는 작업을 수행한다. 즉, './gradlew jacocoTestReport' 또는 './gradlew clean test'를 실행할 때 지정된 클래스와 패키지는 모두 제외한다. jacocoTestCoverageVerification rule의 exclude에서 제외할 클래스 파일명을 추가하는 방법도 있지만 전체 커버리지에 대한 룰이 클래스 파일 제외 전 기준으로 적용되기 때문에 위 메서드를 작업당 한 번씩 호출하는 방법으로 리포트에서 제외시켰다. 아래 리포트 결과를 보면 클래스와 패키지가 제외된 것을 확인할 수 있다.

 

jacocoTestCoverageVerification

 

jacocoTestCoverageVerification {
    violationRules {
        rule {
            element = 'CLASS'

            limit {
                counter = 'BRANCH'
                minimum = 1.0
            }

            limit {
                counter = 'LINE'
                minimum = 1.0
            }
        }
    }
}

커버리지 기준을 만족하는지에 대한 검증을 하는 task로 violationRules로 커버리지 기준을 설정할 수 있다. rule은 위에서 봤듯이 브랜치 커버리지, 라인 커버리지 등 다양하게 기준을 설정할 수 있다.

  • rule 블록: rule 블록은 개별적인 코드 커버리지 검증 규칙을 정의하고, 여러 개 설정할 수 있다.
  • element = 'CLASS': 검증 대상을 설정한 것으로 클래스 수준의 코드 커버리지에 적용된다. 즉, 클래스 전체의 코드 커버리지를 검증하는 규칙이다.
  • limit 블록(커버리지에 대한 제한)
    • count = 'BRANCH', minimum = 1.0: 브랜치 수준의 코드 커버리지가 최소 100% 이상이어야 한다는 제한을 설정한다. 브랜치 커버리지는 조건문에서 발생하는 각각의 분기를 테스트하는 정도를 나타낸다.
    • count = 'LINE', minimum = 1.0: 라인 수준의 코드 커버리지가 최소 100% 이상이어야 한다는 제한을 설정한다. 라인 커버리지는 코드 파일의 각 라인이 테스트되었는지를 나타낸다.

 

Lombok JaCoCo에서 제외

lombok.addLombokGeneratedAnnotation = true

프로젝트 최상단에 lombok.config 파일을 생성해 위와 같이 작성한다. Lombok으로 자동 생성된 메서드는 jacoco에서 제외된다.

 

서비스 레이어에 대한 테스트 커버리지 100% 유지를 목표로!

프로젝트 진행하면서 비즈니스 로직 즉, 서비스 레이어에 대해 테스트 커버리지 100%를 유지를 하려고 한다. 예를 들어, 회원가입이 진짜 잘되는지 확인해 보려는 통합 테스트는 외부 환경까지 연결하여 테스트하겠다는 것인데 중요한 것은 내가 작성한 비즈니스 로직에 대한 테스트이지 데이터베이스까지 제대로 동작하는지에 대한 테스트할 이유는 없다. 다시 말해, 비즈니스 로직이 정상적으로 동작한다면 데이터베이스가 제대로 동작하는지, 컨트롤러가 어떻게 동작하는지 그걸 검증할 이유는 없다는 것이다. 만약 통합 테스트를 짜서 데이터베이스와 동작하는 테스트가 얼마나 깨지기 쉬운지 따졌을 때 DB만 키지 않아도 테스트는 깨지게 된다. 따라서 이러한 코드 외의 영역을 신경쓰면서까지 테스트를 작성하기 보다는 내가 짠 비즈니스 로직에 대한 코드 레벨 테스트만 하는 것이 맞다고 판단하였다.

이제 서비스 레이어에 대한 테스트 커버리지를 100%로 맞추고 앞으로 계속 이를 유지할 수 있도록 하자!

출처

The JaCoCo Plugin - Getting Started

Gradle 프로젝트에 JaCoCo 설정하기

Exclusions from Jacoco Report

JaCoCo 설정