从精准化测试看ASM在Android中的强势插入-JaCoco初探
Posted 冬天的毛毛雨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从精准化测试看ASM在Android中的强势插入-JaCoco初探相关的知识,希望对你有一定的参考价值。
好文推荐:
作者:徐宜生
在Java技术栈上,基本上提到覆盖率,大家就会想到JaCoco「Java Code Coverage的缩写」,几乎所有的覆盖率项目,都是使用JaCoco,可想而知它的影响力有多大,我们在android项目中,也集成了JaCoco,官网文档如下。
https://docs.gradle.org/current/userguide/jacoco_plugin.html
但是这里的JaCoco是与单元测试配合使用的,与一般的业务测试场景不太一样,所以,我们需要自己依赖JaCoco来做改造。
初探
官网镇楼
https://www.eclemma.org/jacoco/
从官网上就能看出这是一个极具历史感的项目。最后生成的覆盖率文件,是在 源代码的基础上,用颜色标记不同的执行状态。
在上面这张图中,绿色代表已执行, 红色代表未执行, 黄色代表执行了一部分,这样就可以算出代码的覆盖率数据。
使用全量报表
JaCoco默认的插桩方式是全部插桩,在Android项目中,要使用JaCoco的全量报表功能非常简单,因为JaCoco插件已经集成在Gradle中了,所以我们只需要开启JaCoco即可。
首先,在根目录gradle文件中加入JaCoco的依赖
`classpath "org.jacoco:org.jacoco.core:0.8.4"`
然后在App的gradle文件中增加插件的依赖。
apply plugin: 'jacoco'
并在android标签中,增加开关。
testCoverageEnabled = true
接下来引入JaCoco的Report模块,同时exclude掉core,因为其在gradle中已经有依赖了。
implementation('org.jacoco:org.jacoco.report:0.8.4') {
exclude group: 'org.jacoco', module: 'org.jacoco.core'
}
创建生成Report的Task
def coverageSourceDirs = ['../xxxx/src/main/java']
task jacocoTestReport(type: JacocoReport) {
group = "Reporting"
description = "Generate Jacoco coverage reports after running tests."
reports {
xml.enabled = true
html.enabled = true
}
classDirectories.setFrom(fileTree(
dir: './build/intermediates/javac/xxxxx',
excludes: ['**/R*.class']))
sourceDirectories.setFrom(files(coverageSourceDirs))
executionData.setFrom(files("$buildDir/outputs/code-coverage/connected/coverage.exec"))
doFirst {new File("$buildDir/intermediates/javac/masterDebug/classes/com/qidian/QDReader").eachFileRecurse { file ->
if (file.name.contains('$$')) {
file.renameTo(file.path.replace('$$', '$'))
}
}
}
}
在项目中合适的地方来调用这两个方法,分别用来创建JaCoco的Exec文件和写入Exec文件。
private void createExecFile() {
String DEFAULT_COVERAGE_FILE_PATH = "/mnt/sdcard/" + getPackageName();
String DEFAULT_COVERAGE_FILE = DEFAULT_COVERAGE_FILE_PATH + "/coverage.ec";
File file_path = new File(DEFAULT_COVERAGE_FILE_PATH);
File file = new File(DEFAULT_COVERAGE_FILE);
Log.d(TAG, "file_path = " + file_path);
if (!file.exists()) {
try {
file_path.mkdirs();
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void writeExecFile() {
OutputStream out = null;
try {
out = new FileOutputStream("/mnt/sdcard/" + getPackageName() + "/coverage.ec", true);
Object agent = Class.forName("org.jacoco.agent.rt.RT")
.getMethod("getAgent")
.invoke(null);
out.write((byte[]) agent.getClass().getMethod("getExecutionData", boolean.class)
.invoke(agent, false));
} catch (Exception e) {
Log.d(TAG, e.toString(), e);
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在创建Exec文件后,进行测试,然后写入Exec文件,等测试完毕后,把生成的Exec文件通过ADB pull到本地,再执行jacocoTestReport这个Task即可生成全量的JaCoco覆盖率报告。
花了这么长时间写了这么多,其实并没什么卵用,只是让大家看下如何来使用JaCoco的标准用法。
JaCoco插桩原理
JaCoco在Android上只能使用Offline mode,它的实现机制其实很简单,我们反编译一下它插入的代码。
可以发现,实际上JaCoco就是用一个Boolean数组来标记每句可执行代码,只要执行过相应的语句,当前位就被标记为True,这个标记,官方称之为「探针」(Probe)。
JaCoco对代码的修改主要体现在下面几个地方:
- 在Class中增加 jacocoData 属性和 jacocoInit方法
- 在Method中增加了$jacocoInit数字并初始化
- 增加了对数组的修改
当然,这只是JaCoco最基本的原理,实际的实现细节会更加复杂,例如条件、选择语句、方法函数的探针插入等等,这里不详细深入讨论,感兴趣的朋友可以参考JaCoco的源码:
https://github.com/jacoco/jacoco
性能影响
由于JaCoco只是插入一个探针数组,所以对代码执行的性能开销影响不大,但是由于插入大量的探针代码,所以代码体积会增大不少,一般情况下,Android会在测试包中做插入,而在正式包中去除插入逻辑。
当然,借助JaCoco还能玩一些骚操作,比如发到线上,实时统计代码中有哪些代码从未执行过,用于发现潜在的垃圾代码。
探针插桩策略
JaCoco的核心逻辑就是要决定,到底在哪插入探针代码。官网文档上对插桩策略写的比较清楚,涉及到字节码的一些原理,所以这里就不深入讲解了,感兴趣的朋友可以通过下面的链接查看。
https://www.jacoco.org/jacoco/trunk/doc/flow.html
关键代码类
JaCoco对代码的探针插入分析,主要是利用了下面这些计数器:
- 指令计数器(CounterImpl)
- 行计数器(LineImpl)
- 方法计算节点(MethodCoverageImpl)
- 类计算节点(ClassCoverageImpl)
- Package计算节点(PackageCoverageImpl)
- Module计算节点(BundleCoverageImpl)
这里面包含了JaCoco的覆盖率数据。
JaCoco的使用其实非常简单,原理也很简单,但要做的好,稳定运行这么多年没有Bug,还是很难的,所以现在市面上做覆盖率的很多软件都逐渐被历史所淘汰了,而剩下的就是经历过时间检验的真金。
小编自己在学习提升时,顺带从网上收集整理了一些 Android 开发相关的学习文档、面试题、Android 核心笔记等等文档,希望能帮助到大家学习提升,如有需要参考的可以直接去我 CodeChina地址:https://codechina.csdn.net/u012165769/Android-T3 访问查阅。
以上是关于从精准化测试看ASM在Android中的强势插入-JaCoco初探的主要内容,如果未能解决你的问题,请参考以下文章
从精准化测试看ASM在Android中的强势插入-读懂diff
从精准化测试看ASM在Android中的强势插入-JaCoco初探