最近业务部门开始推行,在全部后台应用中自动开启覆盖率测试。然而,不久后就有业务测试的同学反馈出现问题。

问题的现象如下:

我们的业务通过 HTTP 调用腾讯OSS的服务,结果得到了以上的报错信息。测试同学验证后发现,关闭覆盖率测试后,问题就消失了。因此,我们可以大致确定是覆盖率引起的问题。

按照经验,覆盖率引起的问题基本上只有一种情况。这里我们可以看看官方文档中的FAQ。

我的代码使用反射。为什么用JaCoCo执行会失败?

为了收集执行数据,JaCoCo 检测被测类,它向类添加两个成员:一个私有静态字段 $jacocoData 和一个私有静态方法 $jacocoInit()。两个成员都被标记为合成的。

请更改您的代码以忽略合成成员。无论如何,这是一个很好的做法,因为 Java 编译器在某些情况下也会创建合成成员。

但这些仅是我猜测的原因。我们需要具体问题,具体分析才行。这个问题比较容易分析,因为我们可以发现报错的原因是传递的参数不合法,所以才会有这样的错误提示。因此,只需对比开启覆盖率和未开启覆盖率两次请求的数据,就可以查看两次请求的差异,进而分析影响请求内容的代码逻辑。

具体分析

尝试1: arthas

private HttpResponse executeOneRequest(HttpContext context, HttpRequestBase httpRequest) {HttpResponse httpResponse = null;try {httpResponse = httpClient.execute(httpRequest, context);} catch (IOException e) {httpRequest.abort();throw ExceptionUtils.createClientException(e);}return httpResponse;
}

跟踪代码我们发现,最后qcloud 的http接口执行会到这里来,而刚好,它这里有一个入参数 httpRequest, 那就可以通过arthas 去watch 一下看看对应的入参的差异内容啦。

watch com.qcloud.cos.http.DefaultCosHttpClient executeOneRequest '{params,returnObj,throwExp}'  -n 5  -x 3

我们看下打印的结果数据

我们发现请求的内容是一个流数据,所以想要通过arthas打印出内容,感觉不太可行了。只能换一种方式

尝试2: 抓包

在抓包上呢 又有一个问题,因为我们请求的域名是一个https的,所以想要抓包具体的包的内容,就有点困难了,所以只能尝试去修改下请求的地址跟协议,因为我们的目的也只是为了能够看到请求的内容而已。

最后我们发现两个请求的差异的内容

插桩后的请求xml格式内容

<Request><Tag>Transcode</Tag><BucketName>cos-public-1304449511</BucketName><QueueId>p870dd99714054da5b311bc70d3110bf9</QueueId><CallBack>http://xxx/cstore/api/v3/callback/async/task/tencent</CallBack><CallBackFormat>json</CallBackFormat><Input><Object>dev-cos-public/90b73c9786584f5e9e7748a73b7bf984.mp3</Object></Input><Operation><Watermark></Watermark><RemoveWatermark></RemoveWatermark><ConcatTemplate><Video></Video><Audio></Audio></ConcatTemplate><Transcode><Container><Format>mp4</Format></Container><TimeInterval></TimeInterval><Video></Video><Audio><Codec>aac</Codec></Audio><TransConfig></TransConfig></Transcode><DigitalWatermark></DigitalWatermark><Output><Region>ap-shanghai</Region><Object>dev-cos-public/b8a483f8b61e4d27baec298752416c1c.mp4</Object><Bucket>cos-public-1304449511</Bucket></Output><PicProcess></PicProcess><Snapshot><SpriteSnapshotConfig></SpriteSnapshotConfig></Snapshot><Segment><HlsEncrypt></HlsEncrypt></Segment><SmartCover></SmartCover><VideoMontage><Video></Video><Audio></Audio><AudioMix></AudioMix></VideoMontage></Operation>
</Request>

未插桩的请求数据

<Request><Tag>Transcode</Tag><BucketName>cos-public-1304449511</BucketName><QueueId>p870dd99714054da5b311bc70d3110bf9</QueueId><CallBack>http://cstore-dev.test.seewo.com/cstore/api/v3/callback/async/task/tencent</CallBack><CallBackFormat>json</CallBackFormat><Input><Object>dev-cos-public/90b73c9786584f5e9e7748a73b7bf984.mp3</Object></Input><Operation><Transcode><Container><Format>mp4</Format></Container><Audio><Codec>aac</Codec></Audio></Transcode><Output><Region>ap-shanghai</Region><Object>dev-cos-public/e5ef796313e0490f9571ede6758253a2.mp4</Object><Bucket>cos-public-1304449511</Bucket></Output></Operation>
</Request>

通过上述的对比,我们就会发现,插桩后的xml请求多出来了很多多余的标签,那我们就要回到代码里面去查看,这个标签是在什么时候被添加的了。

认真查看代码后,我们发现增加具体的标签逻辑是在以下的代码中进行的

private static void addOperation(XmlWriter xml, MediaJobsRequest request) {MediaJobOperation operation = request.getOperation();xml.start("Operation");addIfNotNull(xml, "TemplateId", operation.getTemplateId());addWatermarkTemplateId(xml, operation.getWatermarkTemplateId());addWatermar(xml, operation.getWatermark());addWatermarList(xml, operation.getWatermarkList());addRemoveWatermark(xml, operation.getRemoveWatermark());addConcat(xml, operation.getMediaConcatTemplate());addTranscode(xml, operation.getTranscode());addExtractDigitalWatermark(xml, operation.getExtractDigitalWatermark());addMediaDigitalWatermark(xml, operation.getDigitalWatermark());addOutput(xml, operation.getOutput());addPicProcess(xml, operation.getPicProcess());addSnapshot(xml, operation.getSnapshot());addSegment(xml, operation.getSegment());addSmartCover(xml, operation.getSmartCover());addVideoMontage(xml, operation.getVideoMontage());xml.end();
}

我们看下 addWatermar 的逻辑看看

private static void addWatermar(XmlWriter xml, MediaWatermark watermark) {if (objIsNotValid(watermark)) {xml.start("Watermark");addIfNotNull(xml, "Type", watermark.getType());addIfNotNull(xml, "Dx", watermark.getDx());addIfNotNull(xml, "Dy", watermark.getDy());addIfNotNull(xml, "EndTime", watermark.getEndTime());addIfNotNull(xml, "LocMode", watermark.getLocMode());addIfNotNull(xml, "Pos", watermark.getPos());addIfNotNull(xml, "StartTime", watermark.getStartTime());if ("Text".equalsIgnoreCase(watermark.getType())) {MediaWaterMarkText text = watermark.getText();xml.start("Text");addIfNotNull(xml, "FontColor", text.getFontColor());addIfNotNull(xml, "FontSize", text.getFontSize());addIfNotNull(xml, "FontType", text.getFontType());addIfNotNull(xml, "Text", text.getText());addIfNotNull(xml, "Transparency", text.getTransparency());xml.end();} else if ("Image".equalsIgnoreCase(watermark.getType())) {MediaWaterMarkImage image = watermark.getImage();xml.start("Image");addIfNotNull(xml, "Height", image.getHeight());addIfNotNull(xml, "Mode", image.getMode());addIfNotNull(xml, "Transparency", image.getTransparency());addIfNotNull(xml, "Url", image.getUrl());addIfNotNull(xml, "Width", image.getWidth());xml.end();}xml.end();}
}

我们会发现,这里的重点是在于 objIsNotValid 只有这个为true 才会去添加 Watermark 的标签的。

public static Boolean objIsNotValid(Object obj) {//查询出对象所有的属性Field[] fields = obj.getClass().getDeclaredFields();//用于判断所有属性是否为空,如果参数为空则不查询for (Field field : fields) {//不检查 直接取值field.setAccessible(true);try {Object o = field.get(obj);if (!isEmpty(o)) {//不为空return true;}} catch (IllegalAccessException e) {e.printStackTrace();}}return false;
}

代码的解释也很清楚了,就是通过反射的方式判断所传递的对象中的所有成员变量的属性值是否为空,如果不为空,就会进行添加水印等操作。

我们也断点去看下。

结合上图,很容易就能看出,MediaWaterMark 类在插桩后,将通过反射获取到多一个 $jacococData 成员变量。由于它不为空,前面的判断逻辑就会出现问题,导致水印标签被添加上去。

解决这个问题非常简单,因为网上已经有很多相应的解决措施了。

public static Boolean objIsNotValid(Object obj) {//查询出对象所有的属性Field[] fields = obj.getClass().getDeclaredFields();//用于判断所有属性是否为空,如果参数为空则不查询for (Field field : fields) {//不检查 直接取值field.setAccessible(true);try {Object o = field.get(obj);// 如果是一个合成变量就跳过if (field.isSynthetic()) {continue;}if (!isEmpty(o)) {//不为空return true;}} catch (IllegalAccessException e) {e.printStackTrace();}}return false;}

总结

本文讨论了在使用jacoco覆盖率工具时,由于其插桩导致的反射问题。通过分析传递参数不合法的错误提示,比较开启和未开启覆盖率两次请求的数据,发现插桩后的请求多出了很多多余的标签,最终发现是由于插桩后的类中多了一个 $jacococData 成员变量导致的。解决方法是在判断对象属性是否为空时,跳过合成变量。

排查jacoco覆盖率对反射问题的影响相关推荐

  1. Android ui 单元测试 覆盖率,Android单元测试/Ui测试+JaCoCo覆盖率统计

    Android单元测试/Ui测试+JaCoCo覆盖率统计 参考资料1 参考资料2 背景说明 单元测试 从源代码着手,对源码中的最小可测试单元进行检查和验证,在对源代码有较深的理解下,编写测试单元,工作 ...

  2. sonar jacoco 覆盖率为0_Jacoco统计代码覆盖率

    Jacoco,看起来就很好喝的样子. 一.Jacoco简介 1.Jacoco全称JavaCodeCoverage, 是一个开源的,统计JAVA覆盖率的工具. Python项目是统计不了的 2.Jaco ...

  3. java反射 setAccess,Java反射 - setAccessible的影响(真)Java反射 - setAccessib

    我使用了一些注释动态设置在类字段的值. 因为我想做到这一点,无论它是公共的,保护的,还是私人的,我是一个呼叫setAccessible(true)每次Field对象上调用之前set()方法. 我的问题 ...

  4. Jacoco覆盖率工具使用(已测试)

    笔者踩了很多坑,然后总结以下的研究结果.转载请注明出处,谢谢啦! 1两种方式 1.1 eclipse直接安装插件测试 install new soft 安装插件 eclemma java code c ...

  5. Jacoco覆盖率工具使用

    Jacoco介绍 Jacoco是一个开源的覆盖率工具.Jacoco可以嵌入到Ant .Maven中,并提供了EclEmma Eclipse插件,也可以使用JavaAgent技术监控Java程序.很多第 ...

  6. java代码实现单元测试jacoco覆盖率收集生成多模块聚合报告

    文章目录 背景 一.准备工作 - 生成exec文件 二.准备工作 - 引入依赖 三.利用jenkins-jacoco插件源码收集覆盖率结果 四.生成jacoco报告文件,聚合多模块 附赠相关知识点 背 ...

  7. java jacoco覆盖率报错_接口测试代码覆盖率(jacoco)方案分享

    在做接口测试过程中,为了达到量化接口测试用例效果的目的,引入了代码覆盖率作为重要指标,在查阅相关文档和资料通过实践之后,大概得到了一个方案.如图: 备注:该方案略微复杂了一些,原因在于服务JVM所在的 ...

  8. 影响不良贷款拨备覆盖率的因素分析

    作为商业银行监管信用风险的重要指标,不良贷款拨备覆盖率的作用笔者不再赘述,在上一篇里笔者已经对拨备覆盖率的定义和作用做了解释,拨备覆盖率对于商业银行的重要性不言而喻,今天笔者就影响不良贷款拨备覆盖率的 ...

  9. 单元测试框架和覆盖率统计原理简析

    一 背景介绍 最近部门在推进质量标准化,通过标准化研发.交付.部署.运维等过程,减少缺陷率和返工率,提高整体的工作效率.而单元测试又是软件研发过程中的重要一环,此文可以帮助理解单元测试插件的运行过程, ...

最新文章

  1. FPGA之道(72)提高设计的综合性能(四)提高设计的移植性与保密性
  2. mvc html.hidden,MVC Html.HiddenFor在一个循环传递模型回控制器
  3. java 字符串 数组互转
  4. php中try catch捕获异常实例详解
  5. Windows11右下角出现评估副本水印如何去除?
  6. 打印矩阵 java_【Java】 剑指offer(29) 顺时针打印矩阵
  7. new Date()时间
  8. 2008年卫星地图_黄河入海口1984年-2016年,34年卫星地图变化
  9. SilverLight跨域访问及其常用的几种解决方法
  10. Go 语言的垃圾回收算法被吹过头?与Java比如何?
  11. 计算机锁定无法安装软件,无法安装软件是什么原因,Win10无法安装应用软件的处理方法...
  12. 学前教育试题库及答案_学前教育学考试试题及答案
  13. Gson解析JSON数组
  14. 交换机组播风暴_cisco 交换机端口广播风暴设置(非常详细)
  15. Ext.ux.form.SearchField 添加placeholder属性 2016年9月19日
  16. 开机总是进行磁盘检查
  17. LIO-SAM中的mapOptmization
  18. 内卷失败:敲了 10000 小时代码,我也没能成为一名高级程序员
  19. git --allow-unrelated-histories
  20. 怀旧服服务器荣誉系统是啥,快人一步 争做大元帅 《魔兽世界》怀旧服荣誉系统浅析...

热门文章

  1. Flutter Chip详解
  2. 学Python必看!今年最火的五大Python框架
  3. hdu--2669Romantic
  4. lvgl如何用单向直线触摸滑动条模拟编码器的滚动操作,请注意,单向触摸滑动条是一个输入外设,是一个硬件,而并非lvgl内置的滑条控件...
  5. 解决彩虹六号 2020 第二赛季更新后无法锁亚服
  6. 碧蓝航线服务器维护到几点,碧蓝航线3月7日更新了什么 停服维护内容一览
  7. mysql实现字符串分割
  8. php time 毫秒_PHP microtime()函数获取毫秒时间戳
  9. 升级鸿蒙壁纸还在吗,升级鸿蒙,通知栏米味,桌面果味
  10. oracle 序列 清除,Oracle序列(Sequence)创建、使用、修改、删除