권예진
코딩 공부
권예진
전체 방문자
오늘
어제
  • 분류 전체보기 (57)
    • Git과GitHub (3)
    • 개발상식 (0)
    • Back-End (20)
      • JAVA (3)
      • Spring (2)
      • CI&CD (0)
      • 부스트코스 (15)
    • PS (20)
      • 백준 (20)
    • TIL (0)
    • 회고 (3)
      • 우아한테크코스 (3)
    • 개발 도서 (8)
      • 객체지향의 사실과 오해 (8)
      • 좋은 코드, 나쁜 코드 (0)
    • 일상 (2)
      • 내가 보려고 만든 맥북 꿀팁 (2)
    • etc (1)
      • C (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 스프링부트
  • 객체지향의사실과오해
  • 부스트코스
  • 스프링
  • 우테코
  • git
  • 맥북
  • C언어
  • 우아한테크코스
  • github-actions
  • Jacoco
  • 단계별로풀어보기
  • 독서
  • 프로젝트세팅
  • 백엔드
  • 우아한테크코스5기
  • 우테코5기
  • ps
  • 백준
  • 자바
  • github
  • 윤성우의열혈C프로그래밍

최근 댓글

최근 글

hELLO · Designed By 정상우.
권예진

코딩 공부

프로젝트에 Jacoco 적용하기
Back-End/JAVA

프로젝트에 Jacoco 적용하기

2024. 9. 14. 17:14
반응형

JaCoCo가 무엇인지에 대해서는 이전 글에서 다루었다. (링크: JaCoCo란?)

우아한 기술블로그에 연철님이 작성하신 Gradle 프로젝트에 JaCoCo 설정하기를 참고하여 적용하였다.

 

개발 환경

나는 Java 21, SpringBoot 3.3.3, Gradle 9.0을 사용하고 있다.

 

1. build.gradle의 plugins 블럭에 jacoco 플러그인 추가

plugin {
    ...
    id 'jacoco'
    ...
}

 

2. jacoco 블럭을 만들어서 버전, 테스트 결과 리포트를 저장할 경로 지정

속성으로는 toolVersion과 reportsDir이 있다.

  • toolVersion: JaCoCo 버전 (필수)
    • JaCoCo Releases (GitHub)에서 릴리즈된 버전 정보를 확인할 수 있다.
  • reportsDir: 테스트 결과 리포트를 저장할 경로 (선택)
    • default 값은 ${project.reporting.baseDir}/jacoco이다.

 

나는 이 글을 쓰는 시점에서 가장 최신 버전인 0.8.12를 사용했다.

jacoco {
    toolVersion = '0.8.12'
}

 

터미널에 ./gradlew build 를 입력하여 빌드하고 나면 build 디렉토리 하위에 jacoco 디렉토리가 생성된다.

> ./gradlew build

 

인텔리제이에서 Load Gradle Changes 버튼을 클릭하면 Tasks/verification 디렉토리 하위에 jacocoTestReport 태스크와 jacocoTestcoverageVerification 태스크가 생성된다.

  • jacocoTestReport: 테스트에서 실행된 코드의 커비리지 데이터를 수집하고, 이를 바탕으로 HTML, XML, CSV 등의 형식으로 리포트를 생성한다.
    • HTML 리포트: 웹 브라우저에서 볼 수 있는 시각적 리포트
    • XML 리포트: CI/CD 파이프라인에서 분석하거나 다른 도구와 통합할 때 사용
    • CSV 리포트: 간단한 텍스트 형시으로 커버리지 데이터를 출력
  • jacocoTestCoverageVerification: 테스트 커버리지 기준을 검증하는 역할을 하며, 특정 커버리지 기준에 충족되지 않으면 빌드가 실패하도록 설정할 수 있다.
    • 커버리지 기준 검증: 프로젝트의 클래스, 패키지, 메서드 단위에서 명령어 커버리지, 라인 커버리지 등의 기준을 설정하고, 이를 기반으로 검증 수행
    • 빌드 실패 조건 설정: 커버리지가 특정 기준을 충족하지 않으면 빌드를 실패시키는 규칙을 설정할 수 있다.

 

3. jacocoTestReport 태스크 구성

jacocoTestReport {
    reports {   // 리포트의 형식과 생성 여부 설정
        html.required = true    // HTML 리포트 생성
        xml.required = false    // XML 리포트 생성하지 않음
        csv.required = false    // CSV 리포트 생성하지 않음
    }

    afterEvaluate { // Gradle 빌드 평가가 끝난 후 실행되는 블록이다.
        // JaCoCo 커버리지 리포트에 포함될 클래스 파일 디렉토리 설정
        classDirectories.setFrom(files(classDirectories.files.collect {
            fileTree(dir: it,
                    exclude: [  // 커버리지에서 제외할 파일 패턴 설정
                                '**/*Application*',   // Application 이름이 포함된 파일 제외
                                '**/*Exception*',     // Exception 이름이 포함된 파일 제외
                                '**/*Request*',       // Request 이름이 포함된 파일 제외
                                '**/*Response*',      // Response 이름이 포함된 파일 제외
                                '**/*Dto*',           // DTO 이름이 포함된 파일 제외
                                '**/configuration/**' // configuration 디렉토리 하위 모든 파일 제외
                    ]
            )
        }))
    }
}

 

4. jacocoTestCoverageVerification 태스크 구성

jacocoTestCoverageVerification {
    violationRules {
        rule {
            // 검증할 대상 설정
            element = 'METHOD'

            // 커버리지 기준 설정
            limit {
                counter = 'BRANCH'  // 커버리지를 측정하는 기준
                value = 'COVEREDRATIO'  // 커버리지 계산에 사용할 값
                minimum = 0.50  // 커버리지 값의 최소 허용 값 (지정한 커버리지가 충족되지 않으면 빌드 실패)
            }

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

            excludes = [
                    '*.*Application*',
                    '*.*Exception*',
                    '*.*Request*',
                    '*.*Response*',
                    '*.*Dto*',
                    '*.configuration.*',
            ]
        }
    }
}

 

규칙에는 element, enabled, includes, excludes, limits 등 다양한 속성을 추가할 수 있다.

자세한 내용은 JacocoViolationRule 문서 링크에서 확인할 수 있다.

 

element (ElementType 문서): 커버리지를 검증할 코드 단위 정의

  • BUNDLE (default): 패키지 번들
  • CLASS: 클래스
  • GROUP: Bundle의 논리적 그룹
  • METHOD: 메서드
  • PACKAG: 패키지
  • SOURCEFILE: 소스파일

 

includes: rule을 적용할 대상 정의

  • 와일드카드(* 및 ?) 사용 가능
  • 기본값은 [*] (모든 요소 포함)

 

excludes: rule에서 제외할 대상 정의

  • 와일드카드(* 및 ?) 사용 가능
  • 기본값은 [ ] (빈 리스트) (아무것도 제외하지 않음)

 

limit (JacocoLimit 문서): 커버리지 기준 설정 (세부 속성: counter, value, maximum, minimum)

  • counter (CounterEntity 문서): 커버리지를 측정하는 기준
    • INSTRUCTION (default): Java 바이트코드 명령 수
    • LINE: 빈 줄을 제외한 실제 코드 라인 수 (라인이 한 번이라도 실행되면 실행된 것으로 간주)
    • BRANCH: 조건문 분기 수
    • METHOD: 메서드 수 (메서드가 한 번이라도 실행되면 실행된 것으로 간주)
    • CLASS: 클래스 수 (내부 메서드가 한 번이라도 실행된다면 실행된 것으로 간주)
    • COMPLEXITY: 순환 복잡도
  • value (CounterValue 문서): 커버리지 계산에 사용할 값
    • COVEREDRATIO (default): 전체 항목 대비 커버된 항목의 비율
    • MISSEDRATIO: 전체 항목 대비 커버되지 않은 항목의 비율
    • COVEREDCOUNT: 커버된 항목의 수
    • MISSEDCOUNT: 커버되지 않은 항목의 수
    • TOTALCOUNT: 전체 항목의 수
  • minimum: 커버리지 검증 기준의 최소값 (default는 null)
  • maximum: 커버리지 검증 기준의 최대값 (default는 null)

 

5. 실행해보기

실행해보기에 앞서 테스트 용도의 간단한 클래스와 테스트 코드를 만들었다.

package com.simple.blog.calculator;

public class Calculator {

    public int add(int number1, int number2) {
        return number1 + number2;
    }
}
package com.simple.blog.calculator;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class CalculatorTest {

    @Test
    void addTest() {
        Calculator calculator = new Calculator();
        assertThat(calculator.add(1, 2)).isEqualTo(3);
    }
}

./gradlew build 명령어로 gradle을 clean한 후

./gradlew build -x test 명령어로 test를 수행하지 않고 빌드를 해보면

아래와 같은 빌드 파일들을 볼 수 있다.

 

이 상태에서 ./gradlew jacocoTestReport 명령어를 수행해도 아무런 파일이 만들어지지 않는다.

테스트를 실행한 적이 없기 때문에 테스트 결과 파일이 없기 때문이다.

따라서, jacocoTestReport 태스크를 수행하기 위해서는 테스트 결과가 있어야 한다.

 

./gradlew test 명령어로 test 태스크를 수행해보자.

테스트 관련 빌드 파일이 생긴 것을 확인할 수 있다.

 

그 다음 ./gradlew jacocoTestReport 명령어로 jacocoTestReport 태스크를 수행해보자.

reports 디렉토리 하위에 jacoco 디렉토리가 생성된 것을 확인할 수 있다.

 

build/jacoco/test/html/index.html에서 jacoco 수행 리포트를 확인할 수 있다.

 

./gradlew jacocoTestCoverageVerification 명령어를 이용해 커버리지를 검증해보자.

앞서 Cacluator 클래스와 메서드를 1개 만들었고, 해당 메서드에 대한 테스트가 존재하므로 빌드는 성공적으로 이루어진다.

 

테스트 코드를 삭제하고 다시 검증을 수행해보자.

테스트 코드 삭제 후 ./gradlew test, ./gradlew jacocoTestReport, ./gradlew jacocoTestCoverageVerification 명령어를 순서대로 수행하고 나면 설정한 minimum 조건에 충족하지 못했기 때문에 빌드가 실패한다.

 

지금도 겪었듯이, 이 상태에서는 jacoco를 사용하기 매우 불편하다.

jacoco 테스트 커버리지 기준을 통과했는지 알기 위해서는 항상 test, jacocoTestReport, jacocoTestCoverageVerification 태스크를 순서대로 모두 수행해야하기 때문이다.

 

설정한 기준의 테스트 커버리지를 달성했는지 알기 위해서는 jacocoTestCoverageVerification 태스크가 필요하고,

jacocoTestCoverageVerification는 가장 최근의 jacocoTestReport 태스크 수행 결과를 기준으로 검증을 수행한다.

또, jacocoTestReport를 만드려면 test 태스크를 수행한 결과가 있어야 한다.

따라서, 가장 최근 코드를 기준으로 항상 3가지 태스크를 순서대로 수행해주어야 한다.

 

이러한 불편함을 해결하기 위해 여러 태스크를 하나의 태스크로 묶어서 수행하도록 build.gradle에 설정을 추가해보자.

 

6. 여러 개의 Task를 하나의 Task로 묶기

task 이름은 기존에 있는 task와 겹치지 않는다면 원하는 이름으로 지정할 수 있다.

나는 "jacoco"라는 이름으로 task를 만들 것이다.

build.gradle에 아래 내용을 추가하고 Load Gradle Changes(코끼리 버튼)을 누르면 gradle의 verification 그룹에 jacoco 태스크가 추가된 것을 확인할 수 있다.

tasks.register('jacoco', Test) {
    group 'verification'
    description 'Runs the unit tests with coverage'

    dependsOn(':test',
            ':jacocoTestReport',
            ':jacocoTestCoverageVerification')

    tasks['jacocoTestReport'].mustRunAfter(tasks['test'])  
    tasks['jacocoTestCoverageVerification'].mustRunAfter(tasks['jacocoTestReport'])
}

 

./gradlew {태스크명} 명령어를 수행하면 명령어 하나로 세 개의 태스크를 모두 수행한 것을 확인할 수 있다.

 

7. 테스트 실행 시마다 jacoco 관련 태스크도 자동으로 수행하도록 설정

테스트 실행 시마다 jacoco 관련 태스크도 자동으로 수행하도록 설정하고 싶다면 gradle의 finalizedBy를 사용하면 된다.

finalizedBy를 사용하면 태스크 수행이 끝난 후 finalizedBy로 지정한 태스크를 이어서 수행하도록 할 수 있다.

 

우리는 test 태스크에 이어서 jacocoTestReport 태스크와 jacocoTestReport 태스크를 수행시키고 싶은 것이므로,

test 태스크에 finalizedBy 항목을 추가하여 jacocoTestReport 태스크가 수행되도록 하고,

jacocoTestReport 태스크에 finalizedBy 항목을 추가하여 jacocoTestCoverageVerification이 수행되도록 하면 된다.

test {

    // ...

    finalizedBy 'jacocoTestReport'
}

jacocoTestReport {

    // ...

    finalizedBy 'jacocoTestCoverageVerification'
}

나는 개발 중 테스트 수행 시마다 리포트 생성과 검증이 수행되는 것이 귀찮을 것 같아 굳이 추가하지는 않았다.

반응형
저작자표시 (새창열림)

'Back-End > JAVA' 카테고리의 다른 글

Jacoco란?  (0) 2024.09.14
try-with-resources란?  (0) 2023.04.05
    'Back-End/JAVA' 카테고리의 다른 글
    • Jacoco란?
    • try-with-resources란?
    권예진
    권예진

    티스토리툴바