自动化工具 jacoco 收集测试覆盖率的时候,怎么避免代码改动导致覆盖率清零

jacky · 2021年02月22日 · 最后由 saii 回复于 2022年02月11日 · 4151 次阅读

写了一个简单的 spring demo,2 个 Java 文件
1,用 postman 发请求,覆盖了 Java1,执行 ant dump , ant report 看到 Java1 覆盖率 100%
2,修改 Java2 代码,提交,部署服务
3,执行 ant dump , ant report 发现 Java1 和 Java2 的覆盖率都是 0

这种明显不满足日常测试需求,即使是统计差异覆盖率 (测试分支和 master 分支),测试分支代码改动 覆盖率也变成了 0,有什么好的办法?

共收到 23 条回复 时间 点赞

+1

exec 文件合并了解一下~~

jacoco 在 agent 模式下的覆盖率是在应用内存中以 map 形式进行存储,修改代码后重新部署肯定会清空,还有即使 ant dump 保存到本地,如果 class 文件出现变更也不会合并成功。
也就是你第一次部署,执行 ant dump,本地 exec 文件保存 java1、java2 的覆盖率,修改 java2,部署后,再次执行 ant dump,ant merge 合并,ant report 查看报告,java1 可以展示 2 次部署的总覆盖率,java2 由于文件出现变更,只显示最新的覆盖率。

hacrun 回复

ant merge 是 exec 文件合并吗?我合并 exec 文件后 ant dump,2 个文件的覆盖率都是 0

zhou 回复

文件合并试过了,不起作用的

你的 class 文件是怎么来的?report 覆盖率是 0 不一定真的是覆盖率为 0,也可能是打包的方式或者版本问题,导致 classid 对不上。你可以先对 exec 文件做 execinfo 操作,看下是否有覆盖数据,merge 后再看下 merge 后的 exec 文件有没有覆盖数据。先确定是 exec 文件的问题还是 report 的时候出的问题。
建议可以先用 jacococli.jar 进行操作 (java -jar jacococli.jar --help)。操作的时候可以看下日志。

zhou 回复

我试试这种方式。。
我是用 Jenkins 拉代码,执行 ant 命令的,因为代码改动了 jar 包变了,所以覆盖率为 0

zhou 回复

第一次执行后覆盖率如下,ant report 生成报告:
359ada1e6c61023e 3 of 3 com/example/spring/http/SwaggerRestful
695ddd1b39e5b86d 13 of 32 com/example/spring/http/HttpServer
修改代码后部署服务,执行操作后查看覆盖率,,ant report 生成报告:
2f7ac4ed6385f513 9 of 32 com/example/spring/http/HttpServer
359ada1e6c61023e 1 of 3 com/example/spring/http/SwaggerRestful

exec 文件合并后覆盖率:
2f7ac4ed6385f513 9 of 32 com/example/spring/http/HttpServer
359ada1e6c61023e 3 of 3 com/example/spring/http/SwaggerRestful
695ddd1b39e5b86d 13 of 32 com/example/spring/http/HttpServer

report 的时候用合并后的 exec 文件,classes 如果是第一次的 则报告和第一次的一样,classes 是第二次的则报告和第二次的一样

有什么办法可以把 2 次的报告合并导一起?

jacky 回复

有点麻烦 除非你自己去改 jacoco 的源码,因为 jacoco 的源码逻辑是根据 classId 进行合并的,不是按照 className 进行合并的,所以你虽然两个类包名类名都一样,但是 classId 是不一样的。我们自己是做了二次修改,根据 className 做了一次合并处理

jacky #10 · 2021年02月25日 Author
saii 回复

您的意思是修改 merge 的源码?

jacky 回复

https://github.com/jacoco/jacoco/blob/master/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataStore.java#L48

public void put(final ExecutionData data) throws IllegalStateException {
        final Long id = Long.valueOf(data.getId());
        final ExecutionData entry = entries.get(id);
        if (entry == null) {
            entries.put(id, data);
            names.add(data.getName());
        } else {
            entry.merge(data);
        }
    }

是的,你可以看下这块的逻辑,这个就是按照 classId 然后做数据的合并的。所以如果一定要 只能修改这块的逻辑

jacky #12 · 2021年03月02日 Author
saii 回复

感谢!已解决。

saii 回复

请教一下使用 className 合并的话,探针数组如何与 class 中的方法进行绑定呢

明飞 回复

看你的需求是想要做什么了,我们是做了一层二次处理,如果 id 找不到探针数据,就用 className 再次找一遍做个保底的情况,所以我们在 ExecutionDataStore 中 又增加了一个 private final Map<String, ExecutionData> classEntries = new HashMap<String, ExecutionData>();
这个变量去维护 className 跟探针的关系

jacky #15 · 2021年03月04日 Author
saii 回复
Map<String, ExecutionData> 

只用这一个 map 就够了吧,把 className 作为 key

jacky 回复

嗯 也是可以的。

saii 回复

请问如果 class 中新增了一个方法,导致探针数组变更,按照 className 去合并,会不会导致合并的结果不正确呢

明飞 回复

针对 class 中新增方法这种情况,建议这个类就不要做合并的动作了,因为 jacoco 的源码中不单单是针对不同的 classId 不能合并,就算你改成了 className, 但是他还会去判断探针数组长度,两个探针数组长度不一样也不会合并成功,就算你强行要合并 合并的逻辑又是什么呢?

saii 回复

你好,请问你们二次修改后成功了吗?这个问题也困扰我很久了,请问可以发我一份修改过的代码?非常感谢

sunflower2879 回复

这个其实修改很容易的,因为我那边是改了很多其他部分的东西,不能发你。 你可以看下这篇文章。里面写到了关于如何修改的逻辑

https://blog.csdn.net/qq744746842/article/details/111713216

saii 回复

好的,谢谢你。是否方便加个微信呢

saii 回复

你好,问一下,什么情况下,两个探针数组长度会不一样?

大胡子 回复

一旦两个版本对应的 代码文件被修改了比如说增加了一行,这个时候探针的数据长度就不一样了呀。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册