Szhangbiao's blog

记录一些让自己可以回忆的东西

0%

单元测试覆盖率 Jacoco 集成过程

jacoco 是一款面向 java 的代码覆盖率分析工具,通过 ASM 字节码插桩技术,计算被测试代码覆盖的代码块,最后生成代码覆盖率报告。插桩方式分为在线(on the fly)和离线(offline)两种模式。其中在线模式在使用中更方便。

创建 jacoco.gradle 文件

首先在项目根目录创建script文件夹,然后 jacoco.gradle 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
apply plugin: 'jacoco'

tasks.withType(Test) {
jacoco {
includeNoLocationClasses = true
excludes = ['jdk.internal.*'] // Allows it to run on Java 11
}
}

android {
testCoverage {
jacocoVersion "0.8.10"
}
buildTypes {
debug {
testCoverageEnabled true
}
}
}

// 根据你productFlavor和buildType定义
def sourceName = 'devDebug'
def testTaskName = "test${sourceName.capitalize()}UnitTest"

// 如果需要统计androidTest的覆盖率,需要把createDevDebugCoverageReport这个task加到dependsOn里
// createDevDebugCoverageReport - this task for generate androidTest report, that need connect a devices
task jacocoTestReport(type: JacocoReport, dependsOn: ['testDevDebugUnitTest']) {
group = "JacocoReport"
description = "Generate Jacoco coverage reports on the ${sourceName.capitalize()} build."

reports {
xml.getRequired().set(true)
csv.getRequired().set(false)
html.getRequired().set(true)
// 这里重新定义jacoco生成的报告的位置
//to create coverage report in html
html.outputLocation.set(file("${buildDir}/reports/coverage"))
//for XML
xml.outputLocation.set(file("${buildDir}/reports/jacoco.xml"))
}

def fileFilter = [
// adapters generated by moshi
'**/*JsonAdapter.*',
//dao dir generated by room
'**/dao/**',
// Injector generated by hilt
'**/*GeneratedInjector.*',
'**/*EntryPoint.*',
// data binding
'**/databinding/**/*.class',
'**/databinding/*Binding.class',
'**/databinding/*',
'**/bumptech/*',
'**/BR.*',
// android
'**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Test*.*',
'android/**/*.*',
// kotlin
'**/*MapperImpl*.*',
'**/*$ViewInjector*.*',
'**/*$ViewBinder*.*',
'**/BuildConfig.*',
'**/*Component*.*',
'**/*BR*.*',
'**/Manifest*.*',
'**/*$Lambda$*.*',
'**/*Companion*.*',
'**/*Module*.*',
'**/*Dagger*.*',
'**/*Hilt*.*',
'**/*MembersInjector*.*',
'**/*_MembersInjector.class',
'**/*_Factory*.*',
'**/*_Provide*Factory*.*',
'**/*Extensions*.*',
// sealed and data classes
'**/*$Result.*',
'**/*$Result$*.*'
]

def javaClasses = []
def kotlinClasses = []
def javaSrc = []

rootProject.subprojects.each { proj ->
// ignore app project that contain many deprecated code
if (proj.name != 'app') {
javaClasses << fileTree(dir: "$proj.buildDir/intermediates/javac/$sourceName/classes", excludes: fileFilter)
kotlinClasses << fileTree(dir: "$proj.buildDir/tmp/kotlin-classes/$sourceName", excludes: fileFilter)
javaSrc << "$proj.projectDir/src/main/java"
}
}

sourceDirectories.from = files([javaSrc])
classDirectories.from = files([javaClasses, kotlinClasses])
// 这里注意定义executionData的文件夹位置
def executionDir = "${project.buildDir}/outputs/unit_test_code_coverage/${sourceName}UnitTest/${testTaskName}.exec"
executionData.from = files(executionDir)
print("executionData Dir:$executionDir")
}

由于最新版 Android Studio 生成.exec文件的位置不再是build/jacoco,所以这里要定义具体生成.exec文件的位置。而是会放在 app/build/outputs/unit_test_code_coverage/目录下。

添加 Jacoco 相关配置

首先 classpath

1
classpath('org.jacoco:org.jacoco.core:0.8.10')

然后在app下的 gradle 加入

1
apply("${project.rootDir}/script/jacoco.gradle")

在每一个非 Application 的 gradle 下加入

1
2
3
4
5
6
7
8
9
10
11
12
plugins {
id("com.android.library")
id("jacoco")
}

android {
buildTypes {
debug {
isTestCoverageEnabled = true
}
}
}

命令行得到代码覆盖率统计报告

待工程Sync完成以后就可以运行一下 Command 得到测试代码覆盖率的可视化报告

1
./gradlew app:jacocoTestReport

最后查看 app/build/reports/coverage/jacocoTestReport.xml查看代码覆盖率报告