有在使用 Espresso 撰写测试程序的人应该都知道,在 Android Studio 中 Android Test 类型的 Configuration 是不能使用 Code Coverage 的,最少在 Android Studio 2.2.2 仍然是如此。也就是说“Run 'xxx' with Coverage”的按钮没有办法按,情况如下图所示:
这点和 JUnit 的 Configuration 不同:
产出 Coverage Report 的第一步
以上的限制,对认真撰写测试程序的人来说,会造成很大的不便。Code Coverage 是撰写测试程序的一项重要指标,没有了 Code Coverage 就如同在伸手不见五指的黑暗中行走,完全不知身在何处,有关更多 Code Coverage 用途的细节,请参考先前的这篇文章。
不过,说是做了半套,代表并不是完全没有办法产生 Coverage Report。要在 Espresso 测试运行的过程中产生 Coverage Report,要先调整 build.gradle
,在 buildTypes
设定中开启 testCoverageEnabled
选项。
android {
buildTypes {
debug {
testCoverageEnabled = true
}
}
}
完成以上修改,并执行 ./gradlew createDebugCoverageReport
指令成功之后,就可以在 Module 的 /build/reports/coverage/debug/
路径下,以 Browser 开启 index.html
。
以上的步骤在搜寻之后,都可以取到一堆的文件提供相关的操作细节。但本篇到这里还没有结束,这个方法仅适用于测试和待测对象在同一个 Module 中。如果是在不同的 Module 里,由 Coverage 的 Report 页面中,其实可以看出清单中的 Package 都只列出测试程序所在 Module 的 Class。
扩增 Coverage Report 的范围
假设有一个如下所示的项目结构:
+ Sample
+ app
+ src
+ androidTest
+ main
+ com.sample.app
+ test
+ domain
+ src
+ main
+ com.sample.domain
其中 app
是 Android Application,domain
是 Android Library。如果把所有的测试 Class 放在 app
的 Module 之下,在 test
路径下的测试可经由 Configuration 的 Code Coverage 画面,来把 domain
中的 Class 列入 Report 产生的范围。
在 androidTest
中的测试就没有这么直接了,以 Android 的 Gradle Plugin 官方文件内容来看,目前并没有相关可调整的参数。所以这个阶段的目标,就是让 androidTest
产出的 Report 和 test
一样,可以把指定位置的 Class 列入 Coverage 的范围内。
要达到这个目标,首先要引入 JaCoCo 的 Plugin,透过 JaCoCo 的参数调整来突破原本的限制。在 JaCoCo 中有 sourceDirectories
和 classDirectories
二项参数,用来指定 Report 要涵盖的范围。以上面专案结构的例子来说,需要产生以下的内容在 app
的 build.gradle
中:
apply plugin: 'jacoco'
task jacocoTestReport(type: JacocoReport, dependsOn: "createDebugCoverageReport") {
group = "Reporting"
description = "Generate Jacoco coverage reports."
reports {
xml.enabled = false
html.enabled = true
}
def classFilter = ['**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Test*.*',
'android/**/*.*']
classDirectories = files(["${project.buildDir}/intermediates/classes/debug",
"${project(:domain).buildDir}/intermediates/classes/debug")
sourceDirectories = files(["${project.projectDir}/src/main/java",
"${project(:domain).projectDir}/src/main/java"])
executionData = fileTree(dir: "${project.buildDir}",
includes: "**/*.ec")
afterEvaluate {
classDirectories = files( classDirectories.files.collect {
fileTree(dir: it, exclude: classFilter)
})
}
}
在这里附带说明一下,其实可以把以上的内容独立成单独的文件,例如:jacoco.gradle
,再透过 apply from: 'jacoco.gradle'
的方式引用。一来可以简化 build.gradle
的内容、方便维护,二来可以避免调整 JaCoCo 的选项时,IDE 频繁地出现要求同步的讯息。
另外有一点需要注意的是,createDebugCoverageReport
产出的 executionData
是以 *.ec 为名称,与一般 JaCoCo 使用的 *.exec 不同。
设定好以上的内容之后,就可以执行 ./gradlew app:jacocoTestReport
。和 createDebugCoverageReport
一样会产出 html 格式的 Report,但不同的是 index.html
会在 app
的 /build/reports/jacoco/jacocoTestReport/html
路径下看到。这个路径是默认的,可以透过参数调整。
修正 Coverage Report 的问题
到这里所有的问题都解决了吗?其实并没有!照着以上的内容所产出的 Report 中,domain
下的 Class 不论测试怎么做,在 Instruction 和 Branch 的 Coverage 都呈现 0%,所以显然有一个环节出现了问题。
花了一番功夫发现 domain
在执行 jacocoTestReport
时都只会产生 release
的 Class。
就算是把 classDirectories 的路径调整成 release
,在产出的 Report 中 Coverage 依然是 0%。目前解决方案是要调整 domain
的引用方式:
dependencies {
debugCompile project(path: ':domain', configuration: 'debug')
releaseCompile project(path: ':domain', configuration: 'release')
}
而在 domain
的 defaultConfig
下也要增加以下的设定:
android {
defaultConfig {
publishNonDefault true
}
}
使用以上内容再执行一次 ./gradlew app:jacocoTestReport
,就可以由 Report 中看到,domain
下的 Class 不再全都是 0% 的状态。
仍需改善的部份
虽然 Report 已经可以顺利的产出,但是与 JUnit 可以在 IDE 中直接检视 Coverage 情况的经验相比,还是有差别。毕竟没有办法直接在 IDE 对照着 Code 就可以了解 Coverage 的情况,要在 IDE 与 Browser 间来回地切换其实非常地不方便。
原本有试着把 createDebugCoverageReport
产出的 executionData
,经由以下【Analyze -> Show Coverage Data...】功能所显示的 Window 载入。但这个功能似乎只能载入 *.exec 的文件,就算是把 *.ec 改为 *.exec,载入之后 Coverage 的状态也都呈现 0% 的结果。可能是真的有格式上相容的问题,所以才会把产出的 executionData 以 *.ec 来区格,避免被 Android Studio 载入。
只能期待未来 Android Studio 在改版时,能将这一部份的功能纳进入,既然在 Plugin 都已经可以产出 Report 了,把 Plugin 的动作整合到 IDE 的 Coverage 按钮上,应该不是什么难事吧!
更多深入的文章请参阅 http://www.jianshu.com/u/fea63707e07f
网友评论