Jacoco 적용하기 - woowacourse-teams/2020-songpa-people GitHub Wiki
Jacoco?
자바 코드의 커버리지를 측정하는 라이브러리이다. 테스트코드를 실행하고 그 결과를 html, xml, csv 등의 리포트 파일로 생성한다. 테스트 결과가 설정한 커버리지 기준을 만족하는지 확인하는 기능도 있다. 커버리지 기준을 만족하지 못한다면 배포를 하지 못하게 막을 수 있다.
코드 커버리지
테스트가 충분히 이뤄졌는지 알 수 있는 지표다. 코드 자체가 얼마나 실행되었는지를 정량적으로 나타낸다. 코드 커버리지는 작성한 코드의 라인이 얼마나 실행되었는지, 조건문이 얼마나 실행되었는지 등을 수치로 나타낼 수 있다.
Jacoco 적용하기
개발 환경
- Java 8
- Gradle 6.4.1
Gradle을 이용해서 Jacoco plugin을 추가한다.
루트 모듈에 플러그인 추가하기
하위 모듈들에 모두 테스트 코드를 작성할 것이므로 공통적으로 의존성 추가를 위해서 루트 모듈의 build.gradle
의 subprojects
에 의존성을 추가해준다.
subprojects {
apply plugin: 'jacoco'
jacoco {
toolVersion = '0.8.5'
}
test {
useJUnitPlatform()
finalizedBy 'jacocoTestReport'
}
jacocoTestReport {
reports {
html.enabled true
xml.enabled false
csv.enabled true
}
finalizedBy 'jacocoTestCoverageVerification'
}
jacocoTestCoverageVerification {
violationRules {
rule {
enabled = true
element = 'CLASS'
limit {
counter = 'LINE'
value = 'COVEREDRATIO'
minimum = 0.60
}
excludes = []
}
}
}
}
- test.finalizedBy 'jacocoTestReport' → jacocoTestReport.'jacocoTestCoverageVerification' 을 설정해 주어야 test 후 jacocoReport 생성과 검증 과정이 자동으로 실행된다.
의존성이 추가되면 Gradle/Tasks/verification
에 다음 두 항목이 추가된다.
-
jacocoTestReport
테스트 결과를 문서화 하는 옵션.
-
jacocoTestCoverageVerification
테스트 코드가 설정한 기준을 만족하는지 검증하는 옵션.
→ 이제 각 모듈별로 gradle → test
를 실행하게 되면 build 디렉토리에 report/jacoco 폴더가 생겨서 문서화된 테스트 결과를 볼 수 있다.
기본 설정 바꾸기
개발환경 혹은 테스트 설정을 바꿔야 하는 경우 test block 내의 jacoco block 에서 설정값을 바꿔준다. 다음은 기본설정이다.
test {
useJUnitPlatform ()
jacoco {
enabled = true
address = "localhost"
classDumpDir = null
destinationFile = file("$buildDir/jacoco/${name}.exec")
dumpOnExit = true
excludeClassLoaders = []
excludes = []
includes = []
jmx = false
output = JacocoTaskExtension.Output.FILE
port = 6300
sessionId = "<auto-generated value>"
}
finalizedBy 'jacocoTestReport'
}
jacocoTestReport 기본 설정 바꾸기
원하는 리포트 형식을 키고 끌 수 있다. 각 리포트 타입마다 원하는 저장 경로를 설정할 수 있다.
jacocoTestReport {
reports {
// 원하는 리포트를 켜고 끌 수 있습니다.
html.enabled true
xml.enabled false
csv.enabled false
// 각 리포트 타입 마다 리포트 저장 경로를 설정할 수 있습니다.
// html.destination file("$buildDir/jacocoHtml")
// xml.destination file("$buildDir/jacoco.xml")
}
}
jacocoTestCoverageVerification 기본 설정 바꾸기
커버리지 기준을 설정할 수 있다.
jacocoTestCoverageVerification {
violationRules {
rule {
// 'element'가 없으면 프로젝트의 전체 파일을 합친 값을 기준으로 합니다.
// 위의 리포트에서 'Total'로 표시된 부분입니다.
limit {
// 'counter'를 지정하지 않으면 default는 'INSTRUCTION'
// 'value'를 지정하지 않으면 default는 'COVEREDRATIO'
minimum = 0.30
}
}
// 여러 룰을 생성할 수 있습니다.
rule {
// 룰을 간단히 켜고 끌 수 있습니다.
enabled = true
// 룰을 체크할 단위는 클래스 단위
element = 'CLASS'
// 브랜치 커버리지를 최소한 90% 만족시켜야 합니다.
limit {
counter = 'BRANCH'
value = 'COVEREDRATIO'
minimum = 0.90
}
// 라인 커버리지를 최소한 80% 만족시켜야 합니다.
limit {
counter = 'LINE'
value = 'COVEREDRATIO'
minimum = 0.80
}
// 빈 줄을 제외한 코드의 라인수를 최대 200라인으로 제한합니다.
limit {
counter = 'LINE'
value = 'TOTALCOUNT'
maximum = 200
}
}
}
}
- enabled : 해당 rule의 활성화 여부를 boolean으로 나타냅니다.명시적으로 지정하지 않으면 default로 true입니다.
- element : 측정의 큰 단위를 나타냅니다.
- includes : rule 적용 대상을 package 수준으로 정의합니다.아무런 설정을 하지 않는다면 전체 적용됩니다.
- limit : rule의 상세 설정을 나타내는 block 입니다.
- counter : 커버리지 측정의 최소 단위를 나타냅니다.이 때 측정은 java byte code가 실행된 것을 기준으로 counting됩니다.counter의 종류는 CLASS, METHOD, LINE, BRANCH 등이 있습니다.
- CLASS : 클래스 내부 메소드가 한번이라도 실행된다면 실행된 것으로 간주됩니다.
- METHOD : 클래스와 마찬가지로 METHOD가 한번이라도 실행되면 실행된 것으로 간주됩니다.
- LINE : 한 라인이라도 실행되었다면 측정이 됩니다.소스 코드 포맷에 영향을 받는 측정방식입니다.
- BRANCH :
if
,switch
구문에 대한 커버리지 측정을 합니다. - INSTRUCTION : jacoco의 가장 작은 측정 방식입니다. (바이트 코드를 읽습니다.)LINE과 다르게 소스 코드 포맷에 영향을 받지 않습니다.
- value : 측정한 counter의 정보를 어떠한 방식으로 보여줄지 정합니다.value의 종류는 TOTALCOUNT, COVEREDCOUNT, MISSEDCOUNT, COVEREDRATIO, MISSEDRATIO 가 있습니다.이름에서 충분히 그 뜻을 유추 할 수 있기때문에 따로 설명은 하지 않겠습니다.커버리지 측정에서 우리는 얼마나 우리의 코드가 테스트 되었는지 확인하면 되기 때문에 커버된 비율을 나타내는 COVEREDRATIO를 사용합시다.
- minimum : count값을 value에 맞게 표현했을때 최소 값을 나타냅니다.이 값으로 jacoco coverage verification이 성공할지 못할지 판단합니다.해당 값은 0.00 부터 1.00사이에 원하는 값으로 설정해주면 됩니다.
- counter : 커버리지 측정의 최소 단위를 나타냅니다.이 때 측정은 java byte code가 실행된 것을 기준으로 counting됩니다.counter의 종류는 CLASS, METHOD, LINE, BRANCH 등이 있습니다.
- excludes
- verify 에서 제외할 클래스를 지정할 수 있습니다.패키지 레벨의 경로를 지정해주도록 합니다.경로에는 와일드 카드로
*
와?
를 사용할 수 있습니다.
- verify 에서 제외할 클래스를 지정할 수 있습니다.패키지 레벨의 경로를 지정해주도록 합니다.경로에는 와일드 카드로
검증 단위 기준
하위 모듈에서 검증 기준 바꾸기
하위 모듈에서 검증 기준을 따로 가져가고 싶다면 하위 모듈의 build.gradle
에 jacocoTestCoverageVerification
옵션을 따로 설정해주면 된다.
QueryDSL 사용 시 QClass 검증 대상에서 제외하기
- 사용하는 엔티티
@Entity
public class Place {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String phoneNumber;
protected Place() {
}
public Place(Long id, String name, String phoneNumber) {
this.id = id;
this.name = name;
this.phoneNumber = phoneNumber;
}
public boolean isSamePlace(Place place) {
return this.name.equals(place.getName())
&& this.phoneNumber.equals(place.getPhoneNumber());
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public String getPhoneNumber() {
return phoneNumber;
}
}
측정하지 않을 클래스로 지정하기
QClass를 측정 대상에서 제외하지 않으면 QClass의 커버리지 까지 측정해서 테스트에 실패한다. 검증 기준에 특정 클래스를 포함시키지 않으려면 exclude
에 패키지 + 클래스
명으로 특정 클래스를 제외하면 된다.
QClass를 검증 대상에서 제외하기 위해 jacocoTestCoverageVerification block
에 다음을 추가한다.
jacocoTestCoverageVerification {
def Qdomains = []
for (qPattern in "*.QA".."*.QZ") { // qPattern = "*.QA","*.QB","*.QC", ... "*.QZ"
Qdomains.add(qPattern + "*")
}
violationRules {
rule {
enabled = true
element = 'CLASS'
limit {
counter = 'LINE'
value = 'COVEREDRATIO'
minimum = 0.60
}
excludes = [] + Qdomains
}
}
}
이후 빌드를 실행하면 빌드에 성공한다.
jacocoReport에서 수집되지 않도록 제외하기
jacoco 검증 기준에서 QClass를 제외했지만 리포트에는 포함된 상태이다.
이를 해결하기 위해서는 jacocoTestReport block
에서 지정한 디렉토리를 report 결과에 포함시키지 않도록 해준다.
리포트에 지정하는 경로는 클래스 경로와 다르게 디렉토리
경로를 잡아줘야한다.
jacocoTestReport {
reports {
html.enabled true
xml.enabled false
csv.enabled true
}
def Qdomains = []
for(qPattern in "**/QA" .. "**/QZ"){
Qdomains.add(qPattern+"*")
}
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it,
exclude: [] + Qdomains)
}))
}
finalizedBy 'jacocoTestCoverageVerification'
}
Lombok
Place 엔티티를 롬복을 적용해서 바꿔본다,
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode
public class Place {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String phoneNumber;
@Builder
public Place(Long id, String name, String phoneNumber) {
this.id = id;
this.name = name;
this.phoneNumber = phoneNumber;
}
public boolean isSamePlace(Place place) {
return this.name.equals(place.getName())
&& this.phoneNumber.equals(place.getPhoneNumber());
}
}
테스트 코드를 실행하고 생성된 리포트를 확인하면 lombok에서 제공하는 메서드들도 포함된다.
lombok이 테스트 커버리지에 영향을 미치는 것을 방지하고 싶다면 루트 프로젝트에 lombok.config
파일을 추가한다.
해당 파일에 lombok.addLombokGeneratedAnnotation = true
옵션을 추가하면 된다.
lombok 관련 메서드가 리포트 수집 과정에서 제외되었다.
Lombok.@Generated를 사용해서 특정 메서드 jacoco 리포트에서 제외시키기
lombok.config
파일을 루트 프로젝트에 추가해주었다면 활성화시킨 옵션으로 인해 Lombok으로 생성하지 않은 메서드도 리포트에서 제외시킬 수 있다.
위의 Place 엔티티에서 isSamePlace(Place place)
메스드를 테스트 리포트 대상에서 제외시켜본다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode
public class Place {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String phoneNumber;
@Builder
public Place(Long id, String name, String phoneNumber) {
this.id = id;
this.name = name;
this.phoneNumber = phoneNumber;
}
@Generated
public boolean isSamePlace(Place place) {
return this.name.equals(place.getName())
&& this.phoneNumber.equals(place.getPhoneNumber());
}
}