马蜂窝技术原创内容,更多干货请关注公众号:mfwtech
测试覆盖率常被用来衡量测试的充分性和完整性,也是测试有效性的一个度量。「敏捷开发」的大潮之下,如何在快速迭代的同时保证对被测代码的覆盖度和产品质量,是一个非常有挑战性的话题。
在马蜂窝大交通、酒店等交易相关业务中,项目的开发和测试实践同样遵循敏捷的原则,迭代周期短、速度快。因此,如何依据测试覆盖率数据帮助我们有效判断项目质量、了解测试状态、提升迭代效率,是我们一直很重视的工作。
对于功能测试而言,通常可以通过充分了解需求、完善的测试用例、接口测试、Review 技术方案等来保证测试充分性。但随着业务规模快速发展,业务逻辑越来越复杂,系统级别交互越来越多,这些方法都不能保证所有的代码一定被全部测试过,也给测试人员带来极大挑战。
说到这儿和大家分享一个因为测试覆盖不充分,影响到线上业务的真实案例。事件起因是项目提测阶段一个微服务 Sonar 扫描没通过,开发同学为了修复 Sonar 发现的问题而重构了一部分历史代码,却导致一个原有发券需求的错误。当天下午运营触发发券后 Bug 出现直接导致生单不可用,并且持续了将近一个小时。这里的问题就是发券功能与此次需求无关,开发人员修改代码后测试人员不知道,也没有经过测试,但跟随本次需求一起上线了,测试用例无法覆盖到。
通过这个例子可以明显地看到正确统计被测代码覆盖率的重要性。当前市面上统计覆盖率的工具很多,但应用到我们的实际场景中通常存在以下问题:
大部分第三方工具都是以支持提测前单元测试的覆盖率统计为主,而支持提测后测试覆盖率的工具则比较缺乏;
第三方工具提供的覆盖率基本都是展示全量的,但大部分情况下,测试人员重点关注的应该是本次新增和修改的部分,而不是耗费过多精力在未修改的部分上;
为了支持多需求并行测试,大交通后端服务采用 QA 隔离环境,而市面上还没有支持多需求隔离环境并行测试的测试覆盖率统计工具。
因此,大交通测试组决定自研工具进行被测代码的覆盖率统计,用代码覆盖结果反向地检查测试人员测试用例的覆盖度和开发人员代码的冗余度,更精准地定位和分析问题,保证产品质量。
结合我们的实际场景,覆盖率统计工具的实现目标如下:
大交通业务后端代码使用 Java 语言开发,主要的业务处理逻辑都在后端,所以工具要优先支持 Java 代码覆盖率统计
可以统计提测后测试过程中不同类型测试的覆盖率,包括:手工测试、回归测试、接口测试、UI 自动化测试等等
支持全量、增量覆盖率统计
支持多需求多环境并行测试,同时统计覆盖率
用户不局限于测试人员,而是所有有测试需求的人员,包括开发和测试等
支持自动化统计,简化操作步骤,无需学习成本,报告简单易懂
我们将覆盖率统计工具命名为 jCover,并将其引入 CI/CD 体系,与内部 Java 平台项目管理及持续集成系统 MONE 打通。工具设计如下图所示:
图 1:覆盖率工具整体架构图
覆盖率统计的大致流程为首先通过获取多环境服务的配置信息,生成指定环境服务的测试覆盖率文件,然后根据需要生成全量覆盖率报告和增量覆盖率报告,最后,存储测试覆盖率报告到指定环境目录下。
jCover 的主要功能包括:
支持多需求多环境并行测试下,同时统计覆盖率
支持全量覆盖率统计
支持增量覆盖率统计
支持自动/手动统计覆盖率
覆盖率报告统一管理
下面围绕以上功能点,为大家介绍 jCover 的作用、特点和具体实现方式。
为了简化操作步骤降低学习成本、报告简单易懂,jCover 支持界面化操作,可以手动生成测试覆盖率报告和查询测试覆盖率报告;查询测试覆盖率报告入口统一,代码测试覆盖率数据清晰,方便数据统计和量化。
图 2:覆盖率报告查询页面
由于使用敏捷迭代,项目多、并行率高,大交通所有微服务均引入测试环境隔离插件,同时支持多项目并行测试。因此,jCover 需要支持多环境并行的测试覆盖率统计。
具体来说,每个测试项目都会有一个单独的环境标识,以隔离环境标识+环境类型+服务名称唯一确定一个覆盖率统计单元;每一个覆盖率统计单元都会生成对应的测试覆盖率统计报告,测试覆盖率统计报告被存储到 jCover 部署的服务器中,存储位置是环境标识对应的服务目录下;同一个服务的不同提测分支同时在多项目中测试时相互间的覆盖率报告不会被覆盖。
多环境并行流程图见下图(目前只支持 QA 环境覆盖率统计):
图 3:多环境并行流程图
例如:隔离环境 gjssqz 和隔离环境 supplyrefund 可以同时进行测试覆盖率统计,隔离环境标识+环境类型+服务名称指定了每个环境下的单个服务测试覆盖率统计结果。
覆盖率工具 UI 展示如下图:
图 4:用「隔离环境标识+环境类型+服务名称」来标识和统计覆盖率
在测试时有时候需要关注全量代码的测试覆盖率,代码全量覆盖率统计过程如下:
(1)查询服务信息
jCover 通过内部 Java 平台项目管理及持续集成系统 MONE 来查询被测微服务的信息:
为了减轻对 MONE 系统的压力,设定每日一次定时拉取全量服务信息,将隔离环境标识、服务名称、容器 IP、监听端口、环境类型和状态存储到数据库。
生成覆盖率报告时根据隔离环境标识和服务名称从 MONE 单服务信息拉取接口查询最新的容器 IP。如果数据库没有就新增服务信息,如果数据库有就更新服务对应 IP。
(2)收集覆盖率数据
为了做覆盖率统计,需要从微服务部署的容器中下载测试覆盖率 Dump 文件。我们采用的解决方案是使用 JavaAgent 代理,首先对微服务部署的容器进行了改造,使得容器支持 JavaAgent 功能;其次为了动态插桩记录被测代码的运行结果,在被测微服务的 JVM 启动脚本中增加 JavaAgent 参数。
覆盖率工具根据 IP 和监听端口通过 JavaAgent 代理从容器中下载 Dump 文件,以环境和服务标识唯一 Dump 文件,并存储到 jCover 所在的服务器。在下载覆盖率数据时采用追加方式,如果该 Dump 覆盖率文件已存在,那么该轮的覆盖率数据会直接在文本末尾进行追加,便于统计整个测试过程的代码覆盖结果。
(3)根据全量覆盖率文件生成全量覆盖率报告
MONE 从微服务容器中拷贝 Class 文件到 jCover 所在服务器指定目录
jCover 通过 Class 文件和 Dump 文件生成全量覆盖率文件
根据全量覆盖率文件生成全量的测试覆盖率报告,把全量的测试覆盖率报告以「隔离环境标识+服务名称+顺序 ID」的维度存储到 jCover 所在服务器的指定目录
最后配置静态资源映射规则生成测试覆盖率报告 URL 并把测试覆盖率报告 URL 存储到数据库中
用户打开测试覆盖率工具前端界面就可以在浏览器中查看步骤 D 生成的覆盖率报告了,绿色表示被覆盖,红色表示未被覆盖,黄色表示部分覆盖:
图 5:全量测试覆盖率报告展示
业务发展到一定阶段后,代码量级很大。主干代码我们默认是正确的代码。当一个新需求提测后,测试人员更关注的其实是针对本次需求更改的代码的覆盖率而不是全部代码的覆盖率,想从全量覆盖率报告中找出本次更改的增量代码的覆盖率是非常困难的。基于此问题我们开发了增量覆盖率统计功能。
增量覆盖率统计和全量覆盖率统计有三个相同的步骤:查询服务信息、收集覆盖率数据、生成全量覆盖率文件。生成全量覆盖率文件后,增量覆盖率统计的不同点是增加了以下处理:增量代码获取、生成增量覆盖率报告,具体实现如下:
(1)Diff 增量代码
分别获取每个微服务的 master 主干和测试分支内容,Diff 测试分支对象和 Master 分支对象,得到增量代码
滤除增量代码中的非.java 文件
循环遍历增量代码得到变化行,标识行变化类型包括:新增、更新
(2)生成增量覆盖率报告
根据增量代码变化行标识无变化的方法
在改变的代码行(改变包括增加和更新)行首加上对应的标识:「增加」的代码以「+++」标识,「更新」的代码以「U」标识,同时删除无变化的方法,生成增量代码覆盖率报告
图 6:增量覆盖率流程
下图是交易服务增量覆盖率报告的截图示例,从图中清晰看出增量行和更新行的测试覆盖率情况。
图 7:增量覆盖率报告展示
当测试人员需要查看最新的测试覆盖率时,可以通过「jCover - 生成测试覆盖率报告」的入口手动触发生成测试覆盖率报告。
图 8:手动统计覆盖率图形化界面
在一个需求的实际测试中被测的微服务通常有多个。因为修改 Bug 等原因,被测微服务经常需要重启,重启后容器销毁,JavaAgent 插桩后的文件也会被删除导致重启前覆盖的代码无法被统计。
如果每次重启每个被测微服务前都需要测试人员手动生成覆盖率报告的话,是个很大的工作量也会经常被遗忘。因此 jCover 通过和发布系统结合实现了覆盖率自动统计功能。具体实现方法为 jCover 提供了自动生成覆盖率文件接口,在服务重启部署时后端发布系统调用此接口,实现自动统计覆盖率,不管服务重启多少次都可以把每次测试时覆盖的代码全部统计到。
之前大交通的后端服务发布系统为 Jenkins,后续升级为了 MONE,两种发布系统下均支持服务部署时自动统计覆盖率。
经过一段时间推广使用和优化,现在大交通所有的需求提测后均使用 jCover,主要体现出以下几点优势:
可以支撑不同类型的覆盖率测试。jCover 可以统计手工测试、接口测试、回归测试、UI 自动化测试等各种类型测试的全部覆盖率,展示为一份覆盖率报告。
及时发现漏测代码。通过使用测试覆盖率工具可以发现测试用例未覆盖的代码,反推测试用例缺失。例如在「虚舱管理」项目中国际交易支付前校验失败发送 MQ 消息没有覆盖,测试人员补充了支付前校验失败发消息的测试用例;在「国际搜索坑位供应商及价格优先级设置」项目中发现由于 Mock 数据的局限性, 带有辅营的加价代码未覆盖,补充了机票+辅营的测试用例,提高测试覆盖度。
发现开发的冗余代码。例如:在「国际机票重复预订优化项目」中发现冗余代码,getBaseOrderDetail 方法无调用方,开发进行了删除。
及时发现测试中的问题并改进,提高测试效率。在「国内供应商资源统一化」项目中测试人员执行了很多测试用例后,使用 jCover 查看覆盖率报告发现应该覆盖的代码未被覆盖,与开发沟通后发现控制这部分代码逻辑的开关是「关闭」状态,此开关应该是在「打开」状态进行测试
为上线代码审批提供数据参考。当团队成员申请代码上线时,开发 TL 可以重点关注未覆盖部分代码,减少线上问题发生。
辅助提升测试人员对被测代码的熟悉度。
目前 jCover 只支持查看每一个类覆盖行和未覆盖行是什么,但是对于一个微服务整体行覆盖率是多少没有统计,我们正在开发增量覆盖率百分比,帮助开发和测试做出准确的判断。目前也在调研前端的覆盖率统计,下一步将实现前端的覆盖率统计工具 jCover。
在日常需求测试中可以采用测试(手工测试、接口测试、回归测试等)+jCover 覆盖分析的测试方法来完善测试场景,减少测试遗漏,确保测试的充分性,但需要注意的是 jCover 统计只能展示哪些代码被覆盖了,代码的正确性还是需要用例的执行结果正确来保障;还有时候开发会漏开发某部分需求,此时依靠 jCover 是无法发现这部分遗漏代码的,因此除了 jCover 之外可以通过参与技术评审、编写用例时参考产品功能矩阵图等多种手段发现问题,全方位保障被测代码的质量。
以上就是对马蜂窝大交通测试覆盖率统计工具 jCover 的分享。当然,统计代码覆盖率仅仅是一种手段,覆盖率高不一定代表质量好,但覆盖率不高的代码质量风险肯定很高。只有清楚在覆盖率统计数据背后反映出的问题,才能从根本上保证软件整体的质量。
本文作者:代春美,马蜂窝测试部-交易测试工程师;孙海燕,马蜂窝测试部-交易测试团队负责人。