编者按:本文源自阿里云云效团队出品的《阿里巴巴DevOps实践指南》,扫描上方二维码或前往:https://developer.aliyun.com/topic/devops,下载完整版电子书,了解阿里十年DevOps实践经验。
在任何业务发展的过程中都会不可避免的面临服务的膨胀,应用复杂度的增加,可持续测试的难度不断增加。一方面,用例集会不断的膨胀,一次 CI 验证要数十分钟,用例的维护成本越来越高,开发效率开始降低。另一方面,我们花了精力写了很多自动化用例,希望能够提高投入产出比,也就是测试的有效性。
提升测试速度
分布式测试
分布式测试的核心思想是通过增加计算资源,并发的对 case 进行执行,并在执行后对测试产生的结构化结果进行解析合并,进而提升单次测试的执行速度。
整个测试的执行过程可以划分为以下三个阶段:
- 测试用例解析与分发
- 分组用例的执行
- 分组测试结果合并
以阿里云某云产品核心团队的某工程为例,该工程拥有 1 万多个单元测试用例,在没有采用分布式测试方案之前,一次 CI 的验证时长超过 4 小时,导致问题发现、修复时长拉长,影响了日常的迭代速度。该团队后采取了分布式测试的方式进行,平均的执行时长被优化到了半小时以内。
分布式测试的本质就是用执行资源的堆叠,去换取更快的执行速度。理论上我们把每一个测试用例拆分到一个容器内执行,可以获得极致的反馈速度。但是并不是所有场景下都适合采用分布式测试,例如用例之间存在依赖,这些用例不能无差别的分布在不同的执行分组。
精准测试
分布式测试很大程度上解决了测试执行的速度问题。但是如果在任何情况下都无差别的执行全量的用例,会存在一些问题:
- 对计算资源的浪费
- 引入了大量的无效执行
- 用例本身稳定性问题导致排查时间浪费
- 在探索测试有效性的过程中,我们引入了精准测试的方案
什么是精准测试?
通过建立测试用例与业务方法的关联关系,在代码发生变化时,精准的推荐出需要运行的用例,进行测试执行与结果反馈。通过精准的圈定测试范围,可以带来效率和速度的双重收益。
精准测试的基本要素
- 测试用例(Case)与应用代码方法(CodeMethod)关联关系的建立。这种关联关系,我们定义为基线。
- 代码发生变更,根据基线中用例与应用代码方法的关联关系,准确推荐出变化的方法、关联的测试用例和变化的测试用例,并进行运行。
如何建立测试基线
- 基线的建立方式有多种,在阿里巴巴内部使用过以下两种方法去建立用例与代码的关联。
- 通过字节码注入的方式,埋入 trace 调用,并在调用中传入用例与业务方法的签名。通过采集 trace 的日志,拿到所有的测试用例与方法调用链路,建立起用例与方法的关联关系。
- 通过 AST 解析的方式。
如何进行用例推荐
在代码发生变更后,会基于代码变更解析出变化的测试用例与变化的业务方法。方法的变化通常是新增、删除、更新。
用例的代码变更情况比较简单,所有新增和更新的用例都会纳入到回归范围。
被测应用代码变更,要分情况考虑:
- 新增的方法:匹配不到任何用例,针对此次变更,开发或测试可能补充了测试用例,也可能未补充,需要进行提示。
- 更新的方法:更新的方法分两种情况,第一种,更新的方法有关联的用例,需要推荐出来;第二种,更新的方法原本就没有关联任何用例,需要提示用户补充测试用例进行覆盖。
- 删除的方法:删除的方法也分两种情况,第一种,该方法本身就不关联任何测试用例,不做任何提示;第二种,该方法关联了一些测试用例,那么考虑到方法有可能只是重构或者更名的情况,这部分关联的测试用例在没有被删除的情况下,也是需要进行回归的。
阿里云某核心云产品,通过使用精准测试的方案,一个迭代了一周左右的代码变更,从原先每次 CI 需要全量执行 3700+用例,到每次 CI 可以精准的执行变更影响的用例范围。速度提升了近一倍,测试范围缩小到不到原先的 1/6。
提升测试有效性
我们希望编写和运行的测试用例能够有效的覆盖代码的逻辑,其中重要的一个着手点是测试覆盖率,通过测试覆盖率来暴露问题,并促进问题的解决。
测试覆盖率
测试覆盖率,在本文中,指的是代码的覆盖率。即行覆盖率,分支覆盖率等。
此外,本文中所有涉及覆盖率采集的示例都以 Java 为例。
如何收集完整的覆盖率
一个应用下通常存在多种不同类型的自动化测试集
- 单元测试
- 手工测试
- API 测试
- WebUI 测试
- 其他
为了能够准确的反映一个应用的完整覆盖率,需要对上述多种自动化测试的结果进行聚合。在阿里,每一个测试的运行都会关联到相应的应用,从而可以对测试结果进行聚合。
为了能够收集所有类型的测试覆盖率,我们做了以下事情:
单元测试
对于单元测试来说,覆盖率数据产生在单测执行的机器上,我们会根据执行机上的原始代码信息,编译后的 class 信息,单测执行后产生的覆盖率数据原始文件,以及变更的代码信息,计算出单元测试的覆盖率报告。
手工/自动化测试
我们实现了一个覆盖率采集客户端,和一个覆盖率采集/报告计算解析的覆盖率平台。通过运维平台将覆盖率采集客户端部署到应用的集成环境,在应用启动时会挂载一个 javaagent 进程。当我们在任意测试平台触发任意类型的自动化测试时,会通知覆盖率平台与覆盖率采集客户端进行交互,完成覆盖率计算原始数据的采集与解析。
另外,在进行发布卡点时,我们会合并相应的单测覆盖率形成完整的覆盖率报告。
测试覆盖率能给研发过程带来哪些价值
- 分析未覆盖部分的代码,从而反推在前期测试设计是否充分,没有覆盖到的代码是否是测试设计的盲点,为什么没有考虑到?需求/设计不够清晰,测试设计的理解有误,工程方法应用后的造成的策略性放弃等等,之后逐步补充测试用例。
- 代码覆盖率高不能说明代码质量高,但是反过来看,代码覆盖率低,代码质量不会高到哪里去,可以作为测试自我审视的重要工具之一。
- 分析变更代码的覆盖情况,从而保证对变更的测试充分,增强发布成功率与信心。
除了上述的“测试覆盖率”,还可以使用相同的技术来统计“线上业务对代码的覆盖情况”。也就是统计出来有哪些代码是被真正的线上业务所用到的,哪些是从来没有被调用到的。从而检测出程序中的废代码,可以逆向反推在代码设计中思维混乱点,提醒设计/开发人员理清代码逻辑关系,提升代码质量。
增量覆盖率
有了完整的测试覆盖率数据之后,就可以让它发挥作用了。但脱离了具体的场景,单独追求一个较高的测试覆盖率数值是没有意义的。如果一味的追求较高的覆盖率的数值,往往带来的的是用例的过度设计。如何健康有效的让项目的测试覆盖率稳步的提升,在阿里巴巴内部,我们更多的是采用关注增量覆盖率的方式来提升整体的测试覆盖率。
什么是增量覆盖率
增量覆盖率是指,某一次测试过程中,变化的代码的测试覆盖情况。
变化的代码=被测分支的代码与目标对比分支的 diff(通常目标对比分支是我们最终会合入的分支)。
增量覆盖率=变化的被覆盖的代码行/变化的代码行。
增量覆盖率的价值
- 发布之前是否存在漏测
- 针对漏测完善用例集
- 增强变更发布的成功率与发布信心
- 通过追求增量覆盖率进而提高被测应用的整体测试充分度
增量覆盖率的应用
在单元测试的时候,会有单元测试的增量覆盖率,在测试/预发等环境进行各种接口自动化/UI 自动化/流量回放/手工测试时,也会有增量覆盖率产生。当增量覆盖率与持续交付流水线进行结合时,能够有效的保障项目质量是在往好的趋势发展。
在阿里巴巴内部的实践中,我们通常会在 CICD 流水线中的关键阶段设置各类卡点,保障在多人协作的场景下,每个开发同学提交进入集成的代码经过充分的自测。在上线前的集成阶段,进入的代码在集成环境中被充分验证后,才会被允许发布上线。
通过增量覆盖率的反馈,开发/测试同学可以针对性的去补充各类测试用例,尽可能的保障在各阶段不存在测试遗漏。同时,在不断的完善测试集的情况下,项目的整体测试覆盖率也会得到健康有效的提升。
线上覆盖率
覆盖率的采集我们通常不会应用到线上环境中,但是如果将覆盖率采集放到线上环境,又能演化出应用瘦身的场景,帮助我们从另外一个角度提升效率。
通常在线业务的服务都会部署多个副本,为了减少风险,我们会在其中的少量副本上进行覆盖率采集。经过一个较长的采集周期后,会生成线上覆盖率报告。在这个时候可以认为被覆盖到的代码都是有效代码,而剩下的那些长时间没有流量覆盖的代码,需要谨慎的考虑删除/重构。通过这样的方式去精简我们的代码,从而降低维护成本。
在阿里巴巴内部,当我们决定对某些维护困难或者腐化明显的应用进行重构时,都会进行一段时间的线上覆盖率采集,并根据报告去指导我们进行代码重构,代码瘦身。
小结
分布式测试为测试速度插上了翅膀,精准测试有效的识别出了测试的范围,增量覆盖率又为测试的不断完备提供了有利的指引,线上覆盖率帮助我们有效的进行应用瘦身。充分利用好这些技术手段进行测试提效,可以让持续交付的过程更加的顺畅。
【关于云效】
云效,云原生时代一站式BizDevOps平台,支持公共云、专有云和混合云多种部署形态,通过云原生新技术和研发新模式,助力创新创业和数字化转型企业快速实现研发敏捷和组织敏捷,打造“双敏”组织,实现 10 倍效能提升。