排查jacoco覆盖率对反射问题的影响
最近业务部门开始推行,在全部后台应用中自动开启覆盖率测试。然而,不久后就有业务测试的同学反馈出现问题。
问题的现象如下:
我们的业务通过 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覆盖率对反射问题的影响相关推荐
- Android ui 单元测试 覆盖率,Android单元测试/Ui测试+JaCoCo覆盖率统计
Android单元测试/Ui测试+JaCoCo覆盖率统计 参考资料1 参考资料2 背景说明 单元测试 从源代码着手,对源码中的最小可测试单元进行检查和验证,在对源代码有较深的理解下,编写测试单元,工作 ...
- sonar jacoco 覆盖率为0_Jacoco统计代码覆盖率
Jacoco,看起来就很好喝的样子. 一.Jacoco简介 1.Jacoco全称JavaCodeCoverage, 是一个开源的,统计JAVA覆盖率的工具. Python项目是统计不了的 2.Jaco ...
- java反射 setAccess,Java反射 - setAccessible的影响(真)Java反射 - setAccessib
我使用了一些注释动态设置在类字段的值. 因为我想做到这一点,无论它是公共的,保护的,还是私人的,我是一个呼叫setAccessible(true)每次Field对象上调用之前set()方法. 我的问题 ...
- Jacoco覆盖率工具使用(已测试)
笔者踩了很多坑,然后总结以下的研究结果.转载请注明出处,谢谢啦! 1两种方式 1.1 eclipse直接安装插件测试 install new soft 安装插件 eclemma java code c ...
- Jacoco覆盖率工具使用
Jacoco介绍 Jacoco是一个开源的覆盖率工具.Jacoco可以嵌入到Ant .Maven中,并提供了EclEmma Eclipse插件,也可以使用JavaAgent技术监控Java程序.很多第 ...
- java代码实现单元测试jacoco覆盖率收集生成多模块聚合报告
文章目录 背景 一.准备工作 - 生成exec文件 二.准备工作 - 引入依赖 三.利用jenkins-jacoco插件源码收集覆盖率结果 四.生成jacoco报告文件,聚合多模块 附赠相关知识点 背 ...
- java jacoco覆盖率报错_接口测试代码覆盖率(jacoco)方案分享
在做接口测试过程中,为了达到量化接口测试用例效果的目的,引入了代码覆盖率作为重要指标,在查阅相关文档和资料通过实践之后,大概得到了一个方案.如图: 备注:该方案略微复杂了一些,原因在于服务JVM所在的 ...
- 影响不良贷款拨备覆盖率的因素分析
作为商业银行监管信用风险的重要指标,不良贷款拨备覆盖率的作用笔者不再赘述,在上一篇里笔者已经对拨备覆盖率的定义和作用做了解释,拨备覆盖率对于商业银行的重要性不言而喻,今天笔者就影响不良贷款拨备覆盖率的 ...
- 单元测试框架和覆盖率统计原理简析
一 背景介绍 最近部门在推进质量标准化,通过标准化研发.交付.部署.运维等过程,减少缺陷率和返工率,提高整体的工作效率.而单元测试又是软件研发过程中的重要一环,此文可以帮助理解单元测试插件的运行过程, ...
最新文章
- expert one on one oracle,数据库表——EXPERT ONE-ON-ONE ORACLE
- 第八届蓝桥杯决赛 磁砖样式(枚举)
- plsql(轻量版)_记录类型1
- android读写文本文件,Android读写文件
- 去重 属性_面试中常问的List去重问题,你都答对了吗?
- 最近程序员频繁被抓,如何避免面向监狱编程?!
- Java学习系列(十三)Java面向对象之界面编程
- QT初探(QT+VS2010)
- Android工程项目打包成SDK(jar或aar格式)
- 使用MobileTerminal修改越狱后的root密码
- python安装math库_Python-math库
- 人工智能是一个骗局?
- 2022凯立德导航懒人包完整版(地图包)绝对可以用
- 天地图经纬度精确拾取的方法
- Kali 实现ARP断网攻击_arp断网攻击_arp欺骗
- Android知识点 200 —— framework/base/cmds 常见的am命令,input,pm命令
- 事件起泡 Event bubbling
- 费曼技巧:一张白纸提高学习效率
- 容器启动失败 ERROR: for log Cannot start service log: OCI runtime create failed: container_linux.go:346
- 江苏诚迈科技笔试题2013