代码覆盖率在敏捷式软件开发过程中的实践
一、前言
代码测试覆盖率是一种度量,它描述了对程序源代码的测试程度,是白盒测试的一种手段,能够直观暴露测试用例无法覆盖到的代码块。作为提升代码质量的利器,爱奇艺开发团队和 QA 团队在如何接入、使用等方面做了一些探索性的尝试与实践。
目前代码覆盖率大多停留在整体层面,没有细化到更小的粒度。在协同开发过程中,如果能精细到需求粒度级别覆盖率,关联到具体的负责人,将能有效提高测试效率和精度。
本文将从代码覆盖率接入和使用的角度,叙述了如何结合代码管理、项目管理等 DevOps 工具进行精细化代码覆盖率统计以帮助提高代码质量。
二、背景
互联网公司软件开发模式多为敏捷式或者迭代式开发模型,弥补了传统开发模式的一些弱点,具有更高的成功率和开发效率,能够先尽快地将产品投向市场,再通过用户的广泛参与,不断修改优化产品,实现产品的快速迭代,更好地适应用户市场变化。
不过随着项目的迭代,功能场景变多,代码工程会愈加庞大复杂,代码质量也因此至关重要。如何在开发测试周期提升代码质量,代码测试覆盖率作为一个可以衡量代码质量的标准,如何接入使用覆盖率数据,是一个值得深入探索实践的方向。
定义
> 代码覆盖(英语:Codecoverage)是软件测试中的一种度量,描述程序中源代码被测试的比例和程度,所得比例称为代码覆盖率。
代码覆盖率的意义
1. 完善测试用例:从代码覆盖率报告可以明确,哪些代码是执行过的,哪些代码是没有执行过的。对于没有执行过的代码,测试人员需要思考下是代码逻辑设计问题还是测试用例问题。如果是代码逻辑问题,那么需要与开发人员和产品人员沟通,达成需求的一致性理解。如果是测试用例问题,那么需要补足缺失的测试场景,尽可能保证所有重要的场景都覆盖到了,避免未测试的代码上线造成服务故障。
2. 提升代码质量:开发人员从覆盖率报告可以获得代码的执行流程走向,帮助理解代码逻辑。分析生产环境的代码覆盖率报告,还可以区分出真实的用户请求执行覆盖情况,区分“有用/无用”代码,有利于优化简化代码逻辑。
3. 代码质量标准:代码覆盖率是一个确切的数值,将模糊定义的代码质量用精准的数据来量化,是白盒测试和代码质量的衡量指标。
从代码覆盖率报告可以明确,哪些代码是执行过的,哪些代码是没有执行过的,从未执行过的代码可以推算出测试用例是否充足,帮助测试人员理清代码逻辑和完善测试用例。代码测试覆盖率作为一项衡量代码质量的重要标准,可以在测试阶段发现隐藏问题,将隐性问题消灭在萌芽状态,减少线上问题发生。
现状
开源的覆盖率实现工具有很多,但是在实际使用中,还需要做比较多的准备工作,经常遇到以下问题:
无法区分增量代码
增量代码是指新修改的代码,从全量代码中区分出增量代码的好处是,让使用者只关注于自己负责的代码,去除干扰,减少人工筛选成本。而覆盖率工具只是用于生成代码覆盖率(全量)的工具,没有区分增量代码。
使用成本高
覆盖率报告只是一份综合的报告,使用者通常只会关心自己的代码覆盖率,在多人协同开发过程中,如何将某一处代码覆盖率与具体的需求(开发人员、测试人员)关联是一个问题。
测试环境多人协同多次修改变化性大,维护成本高
在实际的开发测试周期中,可能会出现修改代码再测试,测试后再修改代码的现象,代码覆盖情况是一个变化的过程。考虑到覆盖率工具只能对固定的代码进行收集,如何减少此变化过程的维护成本是需要解决的。
三、目标和功能
基于以上问题,爱奇艺开发团队在覆盖率生成工具基础上,整合 DevOps 工具,以相对较小的成本快速接入生成代码覆盖率,并支持计算生成代码分支级别、需求级别的精细粒度增量代码覆盖率报告。以下以 Java 为例,简单阐述实现方案和原理。
业务架构
简化接入流程
使用方仅关心如何将生产出来的 java 文件、class 文件和 exec 文件推送到指定位置,即可自动化生成详细的覆盖率报告。
增量代码报告
利用 diff 算法,区分出增量代码,生成单独的增量报告和全量报告。
[增量 &全量代码报告]
代码分支级别代码覆盖率
计算测试分支中(某一个代码分支所占的)代码覆盖率。
[代码分支级别报告]
需求级别代码覆盖率
计算 Jira 中某一个需求(issue 涉及到的)代码覆盖率。
[需求级别报告]
四、技术实现
下面以 Java 为例,简要说明技术实现原理。考虑到目前维护性比较好的覆盖率工具,选择了 JaCoCo 开源工具。
运行原理
[运行原理]
JaCoCo 生成覆盖率报告需要三个依赖数据:源码 Java 文件、编译 class 文件和代码探针打桩执行的结果 exec 文件。
1. 源码 Java 文件和编译 class 文件是通过 CI job 的方式 push 到服务端,exec 文件是 jvm 通过 tcp 连接将内存数据导出到服务端。
2. 汇总三部分文件,利用 JaCoCo 工具生成代码的全量代码覆盖率报告。
3. 从 Gitlab 平台获取到测试分支与 master 分支代码 diff 结果,即增量代码,结合其他三部分数据,生成增量代码覆盖率报告。
4. 通常测试代码分支是多人协作的结果,很多 feature 代码分支都会合并到测试分支中,再通过 git commit 溯源关系,得到增量代码的初始来源分支,结合覆盖率报告可以得到每个代码分支的覆盖率报告。
5. 最后,将 Jira 需求和代码分支关联,得到需求涉及的代码覆盖率报告。
核心技术实现
JaCoCo 改造
JaCoCo 覆盖率报告生成条件是 Java 源代码、编译后的 Class 文件和生成的 exec(jvmexecution data,.exec 类型)覆盖率数据。其中 exec 覆盖率数据是由 probe 探针生成的二进制文件,包括 SessionInfo(此次数据的来源 id、开始时间、结束时间)和 ExecutionData(每个类文件对应的探针 boolean 类型数组)。Jacoco 官方给出导出 exec 数据的方式有三种:File System、TCP Socket Server 和 TCP SocketClient。现有的几种方式存在一定局限性,无法满足灵活自动上报的要求,并且如果 jvm 异常关闭,会导致数据直接丢失。所以,在 TCP Socket Client 基础上进行优化改造,支持了 Schedule 定时上报功能,通过配置化定时参数,允许随时上报,灵活配置。
增量代码覆盖率的生成
利用 git 工具对比测试分支与 master 分支,获取增量代码(新增和修改的代码)。根据增量代码找出匹配的 Jacoco 执行 exec 文件,再利用 asm 框架,分析计算得到增量覆盖率数据。
代码分支级别覆盖率
得到增量代码覆盖率后,再通过 git commit 信息,溯源找到所有代码的初始提交分支。
需求级别代码覆盖率
为了便于关联代码和需求,以及快速识别相关开发和测试负责人,对代码分支的命名做了一些规范。
从代码分支中检索出符合规范的代码,查询 Jira 项目管理系统,进行关联。Jira 需求对应的代码分支级别覆盖率即为需求的代码测试覆盖率。
需求类型 | 分支命名前缀 | 示例
---|---|---
跟版需求 | feature/v{version}/{issue_id}_xxx| feature/v11.7.0/JIRA-9443_xxx
纯后端需求 | feature/backend/{issue_id}_xxx |feature/backend/JIRA-9444_xxx
使用要求
为了提升代码质量和减少线上问题的出现,对增量代码的覆盖率提出了比较严格要求,低于标准的情况都需要备注说明原因。
| 类型 | 标准 |
---|---
| 版本交付标准 | 所有代码增量覆盖率>=90% |
| 测试通过标准 | 需求分支增量覆盖率>=90% |
| 代码上线标准 | 代码分支增量覆盖率>=90% |
覆盖率高不等同于代码质量高,但覆盖率不高说明存在有未测试过的代码,这些代码上线后可能会成为服务稳定性方面的隐性问题。但是一味得追求高覆盖率也是不可取的。一方面,无效的代码(例如判空类型的代码)可能无法覆盖;另一方面,追求高覆盖率付出的人力成本会比较高。
需求测试上线流程
[需求测试上线流程]
测试人员根据需求内容制定测试用例,在测试环境进行测试验证完毕后,根据生成的需求测试覆盖报告,衡量是否达标(检查覆盖率是否达到预期、关键的分支判断是否覆盖等)。如果不达标,需要进行补充用例或者需要开发人员进行代码逻辑修复,再进行重复测试,达标后的需求方可进行审批上线到生产环境。在此过程中,平台会自动生成该需求关联的变更代码覆盖情况,避免了复杂协作环境下其他人代码对该需求影响,测试人员可以较快地发现未覆盖代码,并与上线流程打通,减少未覆盖代码带来的隐性问题上线情况,一定程度上保证了服务质量。
五、总结 &规划
本文介绍了爱奇艺开发团队和 QA 团队在测试覆盖率方面的探索和实践,在代码测试覆盖率工具基础深入研究改造,结合其他开发协作平台,支持更快速的集成接入方式,计算展示了增量代码报告、分支覆盖报告、需求覆盖报告等更多维度报告,为使用者提供了极大的便利性。在平台搭建后,平台能快速接入更多的服务,为测试人员提供更多维度的参考衡量标准,重点后端应用服务代码测试覆盖率从不足 70%提升到 90%+,在一定程度上有效地保证了服务质量。
目前的覆盖率数据是来源于测试环境的,只限于开发测试周期使用。后续会在生产环境的代码覆盖率方向上做出一些尝试,例如,利用生产环境的代码执行的覆盖率报告自动化筛选一些关键接口请求作为拨测用例、将生产环境的流量请求测试环境对比生产环境和测试环境的覆盖率报告差别以衡量改动影响等。
评论