Jacoco ?

Java code coverage. 자바 코드 커버리지를 체크하는 데에 사용되는 오픈소스 라이브러리

  • 코드 커버리지 결과를 보기 좋도록 파일 형태로 저장 가능
    • html, xml, csv 등
  • 설정한 커버리지 기준을 만족하는지 확인 가능




Unit test report

  • app gradle
buildTypes{
		release{ ... }
		debug{ ... }
}
flavorDimensions "flavors"
productFlavors{
		open{ ... }
		dev{ ... }
}







Jacoco 적용

  • project gradle
jacoco_version = '0.8.6'
dependencies{
	...
    classpath "org.jacoco:org.jacoco.core:$jacoco_version"
}
allprojects{
	apply plugin: 'jacoco'
    ...
}
  • app gradle
dependencies{
	apply from: "$project.rootDir/jacoco.gradle"
    ...
}
productFlavors.each{productFlavorName->
	buildTypes.each{buildTypeName->
    	def sourceName, sourcePath
        if (!productFlavorName) {
            sourceName = sourcePath = "${buildTypeName}"
        } else {
            sourceName = "${productFlavorName}${buildTypeName.capitalize()}"
            sourcePath = "${productFlavorName}/${buildTypeName}"
        }
        def testTaskName = "test${sourceName.capitalize()}UnitTest"
        
        // 필터링할 파일들 설정
        def fileFilter = [
                // data binding
                '**/*Binding*.*',
                '**/BR.*',
                // android
                '**/R.class',
                '**/R$*.class',
                ...
                // kotlin
                '**/*MapperImpl*.*',
                '**/*$ViewInjector*.*',
                ...
                // sealed and data classes
                '**/*$Result$*.*',
                '**/*Dto*.*',
                ...
                // adapters generated by moshi
                '**/*JsonAdapter.*',
                ...
        ]

		// 소스 디렉토리
        def coverageSourceDirs = ["src/main/java",
                                  "src/$productFlavorName/java",
                                  "src/$buildTypeName/java"]

        // 소스 디렉토리 내 클래스를 컴파일한 결과인 *.class 파일이 있는 디렉토리 지정
        def javaTree = fileTree(dir: "${project.buildDir}/intermediates/javac/$sourceName/classes", excludes: fileFilter)
        def kotlinTree = fileTree(dir: "${project.buildDir}/tmp/kotlin-classes/$sourceName", excludes: fileFilter)

        // Create coverage task of form 'testFlavorTypeCoverage' depending on 'testFlavorTypeUnitTest'
        // dependsOn : 테스트 수행 후 커버리지 측정이 진행될 수 있도록, 유닛 테스트를 수행하는 태스크 이름으로 지정
        // 사용하는 안드로이드 그래들 빌드 플러그인 버전에 따라 다르게 지정해야 하며, 사용 중인 플러그인 버전은 루트 프로젝트의 build.gradle에서 확인 가능
        // 버전별로 지정해야 하는 이름은 다음과 같음
        // 1.2.3 이하: testDebug
        // 1.3.0 이상: testDebugUnitTest
        task "${testTaskName}Coverage"(type: JacocoReport, dependsOn: "$testTaskName"){
        	group = "Reporting"
            description = "Generate Jacoco coverage reports on the ${sourceName.capitalize()} build."

			// sourceDirectories : 커버리지를 측정할 소스 디렉토리를 지정
            sourceDirectories.setFrom(files(coverageSourceDirs))
            additionalSourceDirs.setFrom(files(coverageSourceDirs))

            // classDirectories : 소스 디렉토리 내 클래스를 컴파일한 결과인 *.class 파일이 있는 디렉토리를 지정
            // 커버리지 측정에서 제외해야 하는 클래스(R, 안드로이드 서포트 라이브러리 등)는 제외
            classDirectories.from = files([javaTree], [kotlinTree])

            // executionData: 커버리지 측정 결과를 저장할 파일 이름을 지정. 플러그인 버전에 따라 다르게 지정
            // 1.2.3 이하: testDebug.exec
            // 1.3.0 이상: testDebugUnitTest.exec
            executionData.from = files("${project.buildDir}/jacoco/${testTaskName}.exec")

            // reports : 커버리지 결과 리포트 형식을 지정
            reports {
            	csv.enabled false // change if needed
                xml.enabled false // change if needed
                html {
                	enabled true
                    destination file("${buildDir}/coverage-report")
                }
            }
        }
    }
}






커버리지 기준 만족하는지 ?

task "${testTaskName}CoverageVerification"(type: JacocoCoverageVerification, dependsOn: "${testTaskName}Coverage") {
	group = "Reporting"
    description = "Generate Jacoco coverage reports and verification and on the ${sourceName.capitalize()} build."

    // sourceDirectories : 커버리지를 측정할 소스 디렉토리를 지정
    ...

    // classDirectories : 소스 디렉토리 내 클래스를 컴파일한 결과인 *.class 파일이 있는 디렉토리를 지정
    ...

    // executionData: 커버리지 측정 결과를 저장할 파일 이름을 지정. 플러그인 버전에 따라 다르게 지정
    ...

	violationRules {
		// 여러 룰 생성 가능
		rule {
		    // 룰 on/off
		    enabled = true

		    // 룰 체크할 단위는 클래스 단위
		    element = 'CLASS'

		    // 라인 커버리지를 최소한 80% 만족시켜야 함
		    limit {
		        counter = 'LINE'
		        value = 'COVEREDRATIO'
		        minimum = 0.80
		    }

		    // 빈 줄을 제외한 코드의 라인수를 최대 200라인으로 제한
		    limit {
		        counter = 'LINE'
		        value = 'TOTALCOUNT'
		        maximum = 200
		    }
		
		    // 커버리지 체크 제외할 클래스들
		    excludes = [
		            //'*.test.*',
		    ]
		}
	}
}
  • 커버리지 기준 만족했을 때

  • 커버리지 기준 만족하지 못했을 때

1. element

커버리지를 체크할 기준(단위)을 정할 수 있음

  • BUNDLE : 패키지 번들(프로젝트 모든 파일을 합친 것)
  • CLASS : 클래스
  • GROUP : 논리적 번들 그룹
  • METHOD : 메서드
  • PACKAGE : 패키지
  • SOURCEFILE : 소스 파일

Default 값은 BUNDLE

2. counter

limit 메서드를 통해 지정할 수 있으며 커버리지 측정의 최소 단위

  • BRANCH : 조건문 등의 분기 수
  • CLASS : 클래스 수, 내부 메서드가 한 번이라도 실행된다면 실행된 것으로 간주한다.
  • COMPLEXITY : 복잡도
  • INSTRUCTION : Java 바이트코드 명령 수
  • METHOD : 메서드 수, 메서드가 한 번이라도 실행된다면 실행된 것으로 간주한다.
  • LINE : 빈 줄을 제외한 실제 코드의 라인 수, 라인이 한 번이라도 실행되면 실행된 것으로 간주한다.

Default 값은 INSTRUCTION

3. value

limit 메서드를 통해 지정할 수 있으며 측정한 커버리지를 어떠한 방식으로 보여줄 것인지를 말함

  • COVEREDCOUNT : 커버된 개수
  • COVEREDRATIO : 커버된 비율, 0부터 1사이의 숫자로 1이 100%이다.
  • MISSEDCOUNT : 커버되지 않은 개수
  • MISSEDRATIO : 커버되지 않은 비율, 0부터 1사이의 숫자로 1이 100%이다.
  • TOTALCOUNT : 전체 개수

Default 값은 COVEREDRATIO




Jenkins jacoco plugin 적용



profile
서비스 핵심 가치를 이해하고, 지속적인 개선을 이끄는 엔지니어(를 지향함)

0개의 댓글