1. 功能测试的痛点
版本提测后,开发往往会说,影响范围比较大,做个主链路或者全量回归吧,我只改了几行代码,为什么要回归这么长时间?等等。
测试完成后,测试往往会说,测试保证测试用例全部执行到位,考虑不到的没办法。
代码改动后的 影响范围评估,以及测试完成后的 覆盖面评估 是个难题,目前大部分是依靠个人经验和业务熟悉度判断大概的范围。
这种评估方式以主观判断为主,缺乏数据支撑,希望能有系统工具提供客观数据,基于数据进行量化评估。
产品迭代越来越多,代码改动会对产品已有功能产生影响,除了依赖 CI 的自动化测试,人工回归也必不可少,其成本和效率需要考虑。
为了保证质量,完美的回归测试是全流程覆盖,但是改动五分钟回归两小时,测试成本巨大,还会有很多无效测试。
理想的回归测试是覆盖修改的内容,用有限的操作发现全部的问题。
如果能建立 代码与用例的映射关系, 当代码发生改动时推荐出关联的用例,就能让测试更 精准地回归,降低成本,提高效率。
整个流程中,基于测试同学提交的 Bug 描述,开发理解,重现,修复 Bug 可能需要花费大量的时间。
如果测试通过执行用例就可以找到出 Bug 的代码反馈给开发,开发修复 Bug 会快很多,开发和测试之间的协同工作就会轻松很多。
目前大部分测试在拿到覆盖率报告后,对报告中染红色的代码,由于不熟悉代码,需要去问开发,进行用例补充。
如果能在覆盖率报告中增加批注功能,开发通过批注方式告诉测试这段代码需要补充什么业务场景用例,这样就能提高效率。
**正向追溯 **的核心是将测试用例和代码关联起来,建立用例代码库,这是推荐回归用例的基础,是重中之重。
将测试用例和代码关联起来的核心是 动态调用链,要获取动态调用链就需要 Agent 注入应用,采集应用运行时数据。
公司有基于 OpenTelemetry 的可观测平台,能提供动态调用链路,为我们构建用例代码库提供了基础能力。
有了生成动态调用链路的能力,接下来就可以构建 用例代码库 了,用例代码库的构建可以采用三种方式。
将用例和其调用链路以快照的形式存储到ES或者对象存储,为后续的用例推荐提供基础库,这里唯一比较麻烦的就是用例代码库的维护。
每次版本的新功能由测试重点测试,测试过程中将其用例与代码关联,作为后续用例推荐的基础。
老功能由用例代码库推荐出用例进行自动化或手工测试或者流量会方法,所以,对用例代码库的维护就尤其重要。
可以为每个快照设定周期,过了这个周期推荐出的用例就要进行人工干预,去分析是否是可用的用例。
关于用例代码库的构建目前还在设计中......
构建了用例代码库后,接着就需要进行 测试用例推荐。
利用现有能力 clone compile 提测分支和 master 代码,进行差异代码获取,再根据差异代码反查调用链路,计算出需要测试的范围。
到这里,虽然能推荐出测试范围,但是可能在准确性方面不尽如人意。
比如,当底层或公共代码发生改动时,由于这些代码关联的用例较多,系统会推荐出大量冗余用例,影响测试效率。
那如何提升推荐的精准度?以方法的级别进行推荐,会产生冗余用例,影响推荐精准度,具体如下:
public static void fun(int a){
if(a == 0){
System.out.println("走了 a=0 的分支");
}else if(a == 1){
System.out.println("走了 a=1 的分支");
}else{
System.out.println("走了 非a=0且非a=1 的分支");
}
}
fun 方法假设用3条用例进行覆盖:
当分支 a = 0 内的代码发生变化,如果精确到方法级别的推荐策略进行推荐,这三个用例都会被推荐出来。
但实际上用例 2 和用例 3 是完全不会受到影响的,这样就产生了冗余用例。
所以,在建立用例代码库的时候,就要将用例与代码分支进行关联,
在进行分支解析时,得到用例执行的分支条件及分支所对应的代码块,推荐时差异代码的计算也要精确到有哪些分支发生变更。
所以,首先将本次提交代码的分支条件与用例库中用例的分支条件进行匹配,匹配一致再对比分支内容有无变化。
如果发生变化,则需要做推荐,如果没有发生变化,就说明它不受影响,也无需推荐。
研发流程中,开发从 master 拉取分支 feature_xxx 进行开发,开发完成后进行自测,冒烟,再提交测试。
测试的内容包含新的需求,以及开发测试根据个人能力、经验、业务熟悉程度进行的影响范围评估。
这些评估方式不客观,无数据支撑,容易产生遗漏或者扩大测试范围。
根据开发代码的改动,包括 SQL 的改动,计算出所有受影响的 Dubbo 接口和 Rest 接口,进而找出关联的功能用例。
首先,开发 MR 代码后,精准测试服务 clone master 分支代码,并 clone 和 compile 提测分支代码,为下一步的获取代码差异做准备。
其次,获取master 分支代码和提测分支代码 的差异,除了 JAVA 代码的变更,还考虑了 SQL 的变更,这些变更也会影响到业务。
然后,通过 ASM 解析提测分支代码的 class 字节码,生成每个类的静态方法调用链,ASM 为我们提供了方便的处理字节码的能力。
访问 class 文件中的类,需要重写 ASM 的 ClassVisitor 的 visit() 方法。
访问 class 文件中的方法,需要重写 ASM 的 ClassVisitor 的 visitMethod() 方法进行方法调用关系解析。
访问 class 文件中的注解,需要重写 ASM 的 ClassVisitor 的 visitAnnotation() 方法。
相对于 Rest 接口,要拿到请求路径,请求路径是写在类+方法注解里的,所以,需要解析类注解获取请求路径,
比如 Spring 中的 @RestController 和 @Controller 等注解。
相对于 Dubbo 接口,要知道 Dubbo 的服务提供者和服务消费者,同样需要解析注解,比如 @DubboService 和 @Service 。
关于识别 Dubbo 接口需要注意两点:
最后,通过差异代码查询静态方法调用链,得到受影响的调用链,再追溯到调用链的入口,即 Dubbo 接口和 Rest 接口。
测试时只需要填写在版本测试过程中涉及到的应用的 Git 地址和提测分支,点个按钮等几分钟就可以获取到结果。
第一步:新增 Git 基础信息并点击执行
第二步:查看结果
目前我们做到了获取受影响的 Dubbo 接口和 Rest 接口,通过什么策略建立接口和测试用例的关联关系还在考虑。
我们希望能做到计算出来的 Dubbo 接口和 Rest 接口直接生成回归用例集,直接执行接口自动化,测试主要精力放在新的功能测试上。
通过静态代码分析出的调用链有几点需要说明:
测试过程中通过使用 Jacoco 代码覆盖率统计测试覆盖了多少代码。注意,是统计测试了多少代码,无法证明测试结果的有效性。
如果代码本身就存在错误,Jacoco 本身是发现不了的,需要测试根据业务场景去验证业务逻辑。
不过,Jacoco 能告诉我们测了多少代码,有哪些没测到的进行分析是否要进行补充测试用例。
微服务架构下,同一个应用会部署在不同的节点,请求根据一定的策略打到不同的节点,收集覆盖率数据就需要考虑到所有节点。
整个研发阶段通过不同的测试类型来保障产品质量,为了保证收集到的覆盖率数据尽可能全,不同研发阶段都需要收集覆盖率数据。
原生 Jacoco 只支持相同代码的覆盖率数据合并,不支持代码发生变动的覆盖率数据合并。
就是说如果在测试过程中,开发修改了代码,那么修改前后收集到的两次覆盖率数据是合并不起来的。
合并的结果永远是最近一次收集到的覆盖率数据,修改代码之前的覆盖率数据会被丢弃。
综上所述,相信测试同学能体会到当前敏捷模式下覆盖率数据收集不全的痛点:
结合公司内部的各种平台,我们做了覆盖率平台,用于测试过程中进行覆盖率数据的收集和报告的获取,具体流程如下:
首先,clone&compile master 和提测分支,master 分支代码一旦下载,整个版本周期就固定住,这样避免开发合代码导致数据错乱。
然后,CI/CD 平台 dump 覆盖率数据,由于应用会部署多个实例,我们也支持了多实例覆盖率数据的合并生成最终的覆盖率文件。
接着,因为我们做的是增量代码覆盖(当然我们也支持了全量),所以将 master 分支和提测分支取差异代码
最后,利用 src 源码,class 字节码,exec 文件生成报告。
这里最重要的点是,如果代码发生变更,我们会把开发每次 commit 代码 clone&compile 并 dump其覆盖率数据进行合并,
最终用最新的 commit 代码和 master 进行增量覆盖率统计。
为了降低测试使用覆盖率平台的门槛,我们尽量做到了在不增加测试工作量前提下快速完成代码覆盖率统计,并拿到覆盖率报告。
在开发第一版的时候由于没有跟 CI/CD 平台打通,徒增了测试同学的工作量。
收集覆盖率信息所需要的代码 Git 信息,分支信息,commit 信息需要测试同学手动填写,在使用体验上不太友好。
所以我们梳理了整个流程,对整个流程进行优化,最终优化到测试同学只需要两步即可完成代码覆盖率统计:
在 CI/CD 平台支持下做到一键注入 Agent,我们只需把 Agent jar 和参数给到 CI/CD 平台 ,平台帮我们对 Agent 构建镜像,完成注入。
原生的 Jacoco 在生成报告之前要分别进行 dump 和 merge 操作,我们把这些步骤都做在了一起,
只需从 CI/CD 平台拿到 Git 信息,分支信息,commit 信息,然后先执行 dump,再 merge ,最后 report,做到一键获取覆盖率报告。
当然,这里还有优化的空间,比如目前我们在统计覆盖率数据的过程中需要源码和编译后的字节码,
源码我们是直接 clone 或者 pull 的,编译是拿到源码后进行本地编译的,
这里潜在的问题是编译特别耗时耗资源,经过测试,基本上一台 4C8G 的机器,同时有 4 个应用编译时 CPU 利用率会飙升到95%,
针对这个问题我们的办法是直接用 CI/CD 平台已经编译好的 jar,拿过来直接解压成字节码文件。
下面就是覆盖率平台,测试在 CI/CD 平台注入 Agent 后,只需要填写几个字段 (重点就是应用名和治理环境) 就可以收集覆盖率报告了。
Jacoco 原生的报告在可读性方面不太友好,测试同学实际上只想知道哪些代码覆盖了,哪些代码未覆盖,
对圈复杂度和指令覆盖度的指标不是太理解,也不太特别关注。因此我们对报告进行了如下优化:
效果如下:
后续还会继续对报告进行优化,方便测试和开发解读,包括如下:
目前为止,我们做了逆向追溯即测试范围的评估,用来解决 测什么 的问题,还做了增量代码覆盖率用来解决 测的怎么样 的问题。
未来,一方面会持续迭代优化现有的能力,还会进一步完善整个精准测试体系,包括如下: