본문 바로가기
프로젝트

JaCoCo + Codecov로 CI 테스트 커버리지 관리하기

by jucheol 2025. 8. 25.

1. 배경

테스트 코드의 품질을 관리하기 위해서는 단순히 테스트가 "통과"하는지만 보는 것이 아니라, 얼마나 많은 코드가 테스트되었는가(커버리지) 도 중요합니다.
특히 CI 환경(GitHub Actions, Jenkins 등)에서 커버리지를 자동 측정하고 PR 단위로 확인할 수 있다면, 팀 전체적으로 코드 품질을 꾸준히 유지하는데 큰 도움이 됩니다.

이번에 저는 Gradle + JaCoCo + Codecov 조합으로 프로젝트에 커버리지 측정을 적용했습니다.


2. JaCoCo란?

JaCoCo(Java Code Coverage)는 자바 프로젝트에서 테스트 커버리지를 측정하기 위한 도구입니다.

JaCoCo에서 기본적으로 제공하는 커버리지 종류는:

  • 라인(Line) 커버리지: 실제 실행된 코드 라인의 비율
  • 브랜치(Branch) 커버리지: 조건문(if, switch 등)의 분기 실행 비율
  • 클래스/메서드 커버리지: 테스트된 클래스와 메서드 비율
  • 사이클로매틱 복잡도(Complexity)

일반적으로 라인 커버리지를 기준으로 최소 품질 게이트를 두고, 브랜치 커버리지는 보조적으로 관리합니다. (브랜치 커버리지는 조건문이 많으면 달성하기 어렵습니다.)


3. Gradle 프로젝트에 JaCoCo 적용

✅ 환경

  • Gradle 8.14.3
  • build.gradle (Groovy DSL)
  • 목표:
    • 라인 커버리지 80% 이상
    • 브랜치 커버리지 60% 이상

✅ 설정 예시

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.5.4'
    id 'io.spring.dependency-management' version '1.1.7'
    id 'jacoco'
}

group = 'com.project'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

jacoco {
    toolVersion = "0.8.12"
}

test {
    useJUnitPlatform()
    finalizedBy jacocoTestReport
    systemProperty "spring.profiles.active", "test"
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

jacocoTestReport {
    dependsOn test
    reports {
        xml.required = true
        html.required = true
        csv.required = false
    }

    // global 패키지 제외 (리포트 생성 단계)
    afterEvaluate {
        classDirectories.setFrom(
                files(classDirectories.files.collect {
                    fileTree(dir: it, exclude: [
                            'com/project/questday/global/**'
                    ])
                })
        )
    }
}

jacocoTestCoverageVerification {
    violationRules {
        rule {
            // global 패키지 제외 (검증 단계)
            excludes = [
                    'com/project/questday/global/**'
            ]

            // 라인 커버리지
            limit {
                counter = 'LINE'
                value = 'COVEREDRATIO'
                minimum = 0.60
            }

            // 브랜치 커버리지
            limit {
                counter = 'BRANCH'
                value = 'COVEREDRATIO'
                minimum = 0.00
            }
        }
    }
}

// check task에 커버리지 검증 연결
check.dependsOn jacocoTestCoverageVerification

repositories {
    mavenCentral()
}

dependencies {
    // Spring Boot Core
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'

    // Lombok
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

    // Redis
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'

    // Validation
    implementation 'org.springframework.boot:spring-boot-starter-validation'

    // Logging
    implementation 'org.springframework.boot:spring-boot-starter-logging'

    // Devtools
    developmentOnly 'org.springframework.boot:spring-boot-devtools'

    // MySQL
    runtimeOnly 'com.mysql:mysql-connector-j'

    // Test
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

    // H2 Database for testing
    testImplementation 'com.h2database:h2'
}

tasks.named('test') {
    useJUnitPlatform()
}
  • jacocoTestReport: HTML, XML 리포트를 생성 (특히 Codecov 업로드용 XML 필요)
  • jacocoTestCoverageVerification: 빌드 시 커버리지 기준 미달이면 실패 처리

4. GitHub Actions + Codecov 연동

JaCoCo는 로컬에서 HTML 리포트를 확인할 수 있지만, CI에서 PR 단위로 확인하려면 Codecov 같은 외부 서비스가 필요합니다.

✅ GitHub Actions 예시

name: CODECOV_CI

on:
  pull_request:
  push:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      # gradlew 실행 권한 부여
      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew

      # JDK 설정
      - name: Set up JDK 21
        uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'

      # Gradle 빌드 및 테스트 실행 (테스트 프로파일 적용)
      - name: Build and run tests with Gradle
        run: ./gradlew clean build jacocoTestReport
        env:
          SPRING_PROFILES_ACTIVE: test

      # Codecov 업로드
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: build/reports/jacoco/test/jacocoTestReport.xml
          flags: unittests
          fail_ci_if_error: true
  • 테스트 실행 후 jacocoTestReport.xml을 Codecov에 업로드
  • PR에 Codecov 봇이 자동으로 커버리지 리포트를 댓글로 남김

5. 결과 확인

🔎 로컬에서 확인

  • build/reports/jacoco/test/html/index.html → 패키지/클래스 단위 라인/브랜치 커버리지 확인 가능

🔎 CI에서 확인

  • Codecov PR 코멘트 → PR 단위로 커버리지 변화 확인
  • 기준 미달 시 ./gradlew check 단계에서 빌드 실패

테스트 커버리지 달성 실패

 


6. 적용 효과

  • 품질 게이트 적용: 커버리지가 기준 미달이면 빌드 실패 → 테스트 코드 부족 구간 빠르게 확인 가능
  • PR 리뷰 개선: 코드 변경이 테스트 커버리지에 미치는 영향 확인 가능
  • 팀 차원의 코드 품질 관리: 장기적으로 테스트 코드 작성 습관 강화
  • 시각화: JaCoCo HTML + Codecov 대시보드로 직관적으로 파악

7. 마무리

JaCoCo와 Codecov를 조합하면, 단순한 테스트 통과 여부를 넘어서 "얼마나 잘 테스트되었는가" 를 CI 단계에서 바로 확인할 수 있습니다.
특히 오픈소스 프로젝트나 팀 협업 환경에서 큰 장점이 있으며, PR 기반의 품질 게이트를 통해 테스트 부족 영역을 빠르게 식별할 수 있습니다.