最近公司要求所有应用都要补充单元测试,AS对单元测试支持非常到位,只需在build.gradle中设置testCoverageEnabled为true即可(无需其他设置):
进行gradle同步,会得到两个新的task:
只需执行这两个task(一个是仪器测试、一个是本地测试),便可一键执行用例并生成html报告。
现象
但是我的测试uid为system的系统应用时,执行完成后,得到的报告中并没有覆盖率(全部为0),后来用了一个简单的demo测试:
不设置sharedUserId为system时一切正常:
找了半天也没看到准确的答案和解决方法,无奈只能自己动手。
原因
通过研究发现,是得到的覆盖率文件coverage.ec大小为0,但是通过adb shell到设备上查看是有正常的coverage.ec的,于是通过命令行加上--debug参数执行:gradlew app:createDebugAndroidTestCoverageReport --debug,果然发现了报错:
Shell command failed (1): run-as xxx cat "/data/data/xxx/coverage.ec" > "/data/local/tmp/12035a40-3985-484c-8cdf-ad5b4240d316-coverage_data/coverage.ec"
run-as: package not an application: xxx
原来,createDebugAndroidTestCoverageReport并不是通过adb pull直接把coverage.ec拖出来,而是通过run-as命令,现将shell的uid切换到被测应用的uid,然后就可以访问应用的data的文件了,再通过cat重定向到/data/local/tmp下的临时文件中
而在系统源码中,在platform/system/core/run-as/run-as.cpp有这样一句:
// Reject system packages.
if (userAppId < AID_APP) {
error(1, 0, "package not an application: %s", pkgname);
}
所以系统uid是不能run-as的。
解决方法
那只能曲线救国了,先通过命令行执行测试,并adb pull取出coverage.ec,然后手搓一个jacoco.gradle的脚本,放到项目根目录下,用来解析pull出来的coverage.ec,得到html报告:
apply plugin: 'jacoco'
android {
buildTypes {
debug {
testCoverageEnabled = true
}
}
}
// 源代码路径,从module路径起算,有多少个module,就在这里写多少个路径,如果你只有app一个module,那么就写一个就可以
def coverageSourceDirs = [
'/src/main/java',
]
// class文件路径,从module路径起算,如果你只有app一个module,那么就写一个就可以
def coverageClassDirs = [
'/build/intermediates/javac/debug/classes'
]
// systemuid的应用,无法直接执行createDebugAndroidTestCoverageReport生成html报告(普通应用可以)
// 需使用命令行运行测试用例,然后通过adb pull设备手动取出coverage.ec,放到outputs\code_coverage\debugAndroidTest\connected\coverage.ec
// 最后手动执行gradlew jacocoTestReport生成报告
task jacocoTestReport(type: JacocoReport) {
group = "JacocoReport"
description = "Generate coverage reports after running tests."
reports {
xml.required = false
html.required = true
csv.required = true
}
classDirectories.from = files(coverageClassDirs)
sourceDirectories.from = files(coverageSourceDirs)
executionData.from = files("$buildDir/outputs/code_coverage/debugAndroidTest/connected/coverage.ec")
}
把这个jacoco.gradle导入导模块的build.gradle中:
apply from: rootProject.file('jacoco.gradle')
这样,就会多出一个叫jacocoTestReport的gradle task,在执行完用例并pull了coverage.ec后,执行该任务即可(记得coverage.ec文件路径要对应)
搞定!!!
注意:
一定不要在AS右侧的task中执行测试任务,那个执行完后会卸载包,那么coverage.ec文件也就被删除了,需要命令行执行:
adb shell "am instrument -w -m -e coverage true -e coverageFile /data/data/xxx/coverage.ec xxx.test/androidx.test.runner.AndroidJUnitRunner"
xxx为你的被测应用包名