[DDSP] Jacoco Report

이프·2025년 1월 9일

back-end

목록 보기
6/16

Jacoco Report란?

Spring boot로 프로젝트 개발을 진행한다면 스프링의 꽃 중 하나인 테스트를 꼭 경험하게 될 것이다.

토비 : 테스트를 작성하지 않을 것이면, 스프링 부트를 왜 사용할까요?

Jacoco Report는 이런 테스트에 대한 Coverage를 체크해주는 도구이다.
예를 들어 테스트에서 클래스 내 각 메서드들을 테스트 했는지?
각 분기에 대해 테스트 했는지?
말 그대로 정보처리기사에 나오는 Test Coverage를 대신 처리해주는 자동화 도구이다.


Jacoco Report를 사용한 배경

테스트 방법으로는 인수 테스트, 통합 테스트, 단위 테스트가 존재한다. 우리 프로젝트에서는 인수 테스트는 따로 진행하지 않고 비즈니스 로직에 집중하기 위해서 서비스 레이어를 제외한 각 계층에서 단위 테스트를 진행했다.

하지만, 첫 프로젝트인만큼 중간 중간 테스트를 놓치는 부분도 있었고 스프린트 기간 안에 기능 개발을 위해 테스트를 일부러 하지 않은 경우도 있었다.

이게 독이 될 줄 몰랐다.

Front 측에서 API에 대해 추가 기능 요구나 버그로 인한 수정 요구 시 코드를 수정하는 작업이 발생할 때마다 스프링 컨테이너를 띄워서 일일이 다 확인해야 됐다.
흠.. 어떻게 개선할 수 없을까?.. 그래서 막 리서치하던 중 우아한 기술 블로그에서 소개하는 Jacoco Report를 알게 된 것이다.

그럼, Jacoco Report로 어떻게 개선되는지 한 번 보자.


Jacoco Report 적용

// Test Coverage
tasks.named('test') {
	useJUnitPlatform()
	finalizedBy jacocoTestReport
}

jacoco {
	toolVersion = "0.8.9"
}


def classDirectoriesFilter = {
	project.fileTree(
			dir: it,
			exclude: [
					'**/deepdivers/community/BackendApplication*',
					'**/deepdivers/community/**/config/**',
					'**/deepdivers/community/**/exception/**',
					'**/deepdivers/community/**/dto/**',
					'**/deepdivers/community/**/entity/**',
					'**/deepdivers/community/**/interceptor/**',
					'**/deepdivers/community/**/resolver/**',
					'**/deepdivers/community/**/generator/**',
					'**/deepdivers/community/global/handler/GlobalExceptionHandler*',
					'**/deepdivers/community/infra/aws/s3/properties/**',
					'**/deepdivers/community/infra/aws/s3/generator/S3RequestGenerator*',
					'**/Q*.class',
					'**/*_.class',
					'**/generated/**'
			]
	)
}

jacocoTestReport {
	dependsOn test

	afterEvaluate {
		classDirectories.setFrom(files(classDirectories.files.collect(classDirectoriesFilter)))
	}

	reports {
		xml.required = false
		csv.required = false
		html.required = true
		html.outputLocation = layout.buildDirectory.dir('reports/jacoco/html')
	}
	finalizedBy jacocoTestCoverageVerification
}

jacocoTestCoverageVerification {

	afterEvaluate {
		classDirectories.setFrom(files(classDirectories.files.collect(classDirectoriesFilter)))
	}

	violationRules {
		rule {
			element = 'CLASS'

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

			limit {
				counter = 'BRANCH'
				value = 'COVEREDRATIO'
				minimum = 0.80
			}

			limit {
				counter = 'METHOD'
				value = 'COVEREDRATIO'
				minimum = 0.80
			}
		}
	}
}
  • java 환경에서 @Test 어노테이션이 JUnit5를 테스트 템플릿 엔진으로 사용하겠다고 명시
  • 끝나면 jacocoTestReport 블록을 실행
    • jacocoTestReport는 JacocoPlugin에서 제공된다.

Jacoco 세팅

plugins {
	id 'jacoco'
}
  • jacoco plugin을 사용하기 위해서 명시해주자!

Jacoco Report Task 세팅

jacocoTestReport {
	dependsOn test
}
  • jacoco Test Report는 test task 중 java class에 대한 모든 테스트가 끝난 뒤에 생성되어야 하므로 테스트가 끝날 때까지 기다리고 있어야 한다.
afterEvaluate {
	classDirectories.setFrom(...)
}
  • 모든 gradle 설정 파일들이 정상적으로 평가되었을 때 발생하는 작업이다.
  • 현재 프로젝트의 Jacoco Report 설정의 afterEvaluate는 build/classes/** 에 존재하는 모든 file들 중 Report에서 제외할 대상을 필터링 한 뒤 새로운 파일 집합을 생성하는 작업을 한다.
  • 즉 Jacoco Test Report에 제외할 녀석들을 선별하는 작업이다.
reports {
	xml.required = true
    csv.required = false
    html.required = true
    html.outputLocation = layout.buildDirectory.dir('reports/jacoco/html')
}

finalizedBy jacocoTestCoverageVerification
  • report는 다른 도구를 사용하지 않고 보기 쉽게 html로 확인하겠다 :)
  • xml은 CI 작업에서 test coverage 결과를 노출하려면 필요하다.
  • 그럼 이제 테스트 할 정보의 report 생성을 마쳤고 이 정보로 coverage를 검증하자!

jacocoTestCoverageVerification Task 세팅

  • 여기서 afterEvaluate는 앞서 설명된 내용과 동일하다.
violationRules {
	rule {
    	element = 'CLASS'
        limit {
        	counter = 'LINE'
            value = 'COVEREDRATIO'
            minimum = 0.80
     	}
    }
}
  • coverage rule을 설정하는 정보이다. 클래스의 line, method, branch 등에 대한 coverage rule을 설정 할 수 있다.
  • 나는 80% coverage를 설정했다.
  • 다만, test task에 의해 무조건 실행되면서 test coverage를 만족하지 못하면 빌드를 할 수 없을 수 있다.
    • 우선 테스트 커버리지를 다 지키지 않고 추후 테스트를 한 번에 하려면 jacocoTestReport 에서 finalizedBy jacocoTestCoverageVerification를 주석처리 해두면 된다.

이제 jacoco report 설정이 끝났다. 결과를 확인해보자!


Jacoco Report Result

# ${PROJECT_ROOT}/gradlew clean test

이제 프로젝트의 build/reports 에서 결과를 확인할 수 있다.
우선, 테스트 결과를 보자!

모든 클래스에 대해 성공적으로 테스트가 됐다는 것을 확인할 수 있다!!

이번엔 Coverage까지 보자.

이제 부족한 테스트에 대해 쉽게 확인 할 수 있다.

사진 결과와 같이 분기나 특정 라인, 메서드 별로 테스트 되지 않은 정보를 확인 할 수 있다!
(resetPassword에서 JPA 더티 체킹을 활용하지 못한게 거슬리네..)


마지막

Trouble Shooting

report 설정에 의문점이 들었던 부분이 있었다.

jacocoTestCoverageVerification TaskJacocoReport Task 이후에 실행된다면, jacocoTestCoverageVerification Task에서는 report 정보가 있으므로 굳이 파일 집합을 안만들어 되지 않나?

  • report 정보로 하는 것이 아니었다.
  • JacocoReport에서 Report를 생성할 때, test가 끝난 정보가 report dir에 test.exec가 생성되는데 이 정보로 테스트 및 커버리지 검증 결과에 대한 페이지를 만들어 줄 수 있다.
  • 만약, jacocoTestCoverageVerification가 먼저 실행된다면 test.exec 파일을 생성할 위치를 설정할 수 없으므로 안되는거였다.

회고

학습 곡선도 그렇게 높지 않았고 JacocoRepository를 적용하면서 부족한 테스트를 한 눈에 확인 할 수 있었다. 또 CI 작업 시, PR에서 좀 더 가시적으로 test coverage 결과를 볼 수 있어서 좋은 기술을 알게 된 것 같다!

Reference

profile
if (이런 시나리오는 어떨까?) then(테스트로 검증하고 해결) else(다음 시나리오 고민)

0개의 댓글