“测完了吗?” 是系统测试岗位同学经常被问到的问题,提问的人可能是合作的研发, 合作的产品经理,甚至是项目的业务方,也有可能是测试自己。
这个问题至少有两层意思,不仅问新功能测试进度是否完成,更重要的是想知道测试的结果怎么样,测试同学如何回答这个问题,背后反映出来的是对本次测试工作的信心。
软件工程理论认为,受限于时间、资源、外部输入、测试人员经验等主客观原因,对一个系统完全测试是不可能的, 但是也正是由于上面的原因,导致了不同的测试人员或者团队,在执行测试的效果上参差不齐。但是作为有追求的测试人员,我们要努力靠近 “完全的测试”。
本文基于申通的业务和技术现状,申通技术质量团队尝试回顾项目测试的全过程,从测试设计,新功能测试,回归等方面给出自己的答案。
在正式开始之前,请允许我花一些时间介绍一下申通技术质量的工作模式。
在软件项目管理中,对质量的关注部分往往放在用例的完成情况以及缺陷的修复情况等结果方面,从一些常见的项目管理工具,如 jira、teambition 等产品设计上也有所体现。
但是为了回答本文问题,光靠这些结果是不够的,因为关键的用例设计,测试执行过程往往缺乏标准化,导致结果的可信度大打折扣。因此,我们需要对测试过程做精细化的管理。从实际情况出发,目前我们着眼在最常见也是最重要的功能测试方面。
我们对常见的项目测试过程进行了抽象,从测试设计、新功能测试、回归等方面进行了标准细化,并且自研了一套 “质量管家” 系统,实现了测试工作流的在线化。不同于市面上的项目管理工具,质量管家在 “工具” 的基础上,融入了流程标准的概念。除了在测试过程中给测试同学提供帮助之外,还可以对测试过程进行标准化管理。
比如我们通过设定了一些测试流水线,对不同类型的项目需求测试进行了流程规范,确保关键的测试动作执行到位,同时也通过比如测试评审纪要功能,对评审待办事项进行自动跟踪,避免待办被遗忘,测试完成自动发送报告,节省测试同学书面工作时间。
产品示意:
得益于质量管家实现的过程量化,我们具备了回答 “测完了吗?” 问题的数据基础能力,可以基于此做质量过程和结果量化。 下文我们从几个关键点展开论述。
虽然自动化测试是很多测试团队追求的方向,但是从申通的产品特性出发,综合考虑测试 ROI,传统手工测试(人工测试)还是占据了绝大多数的时间。所以我们把重点放在人工执行的功能测试,其中的几个关键点,包括了用例设计完整性,新功能测试完整性,回归充分性,以及对上述环节的不断优化迭代。
更多内容可以学习《测试工程师 Python 工具开发实战》书籍、《大话性能测试 JMeter 实战》书籍
传统的测试用例设计,基于经典的软件工程理论,大致上包含 8 要素:用例编号、所属模块、测试标题、重要级别、前置条件、测试输入、操作步骤、预期结果。 规范的测试用例通常会用一个表格的形式来展现,我相信很多从事软件测试时间比较久的同学都经历过。
经典的实践结合测试用例设计理论(等价类,边界值等)可以一定程度上确保测试用例设计的完整性,但这是前互联网时代的经验。在当前我们很多时候要求快速迭代,快速上线;技术团队,包括测试,没有办法在前置的书面分析工作上投入足够的时间,加上很多测试团队没有在测试用例方面下功夫做管理,于是很多互联网技术团队的测试用例就简化到了一句话,甚至没有大纲。
这样导致的结果就是,很多测试需要关注的细节,由于开始没有被书面记录到用例中,导致真正在测试执行的过程里,用例执行凭感觉,缺陷发现看经验,甚至看运气,很多经典测试中好的实践被抛弃,导致测试遗漏的问题常常出现。
那么如何平衡完整性和效率之间的关系,更好地适应互联网的开发模式?经过思考,我们吸收了部分经典实践中的关键部分,摒弃了一些无关紧要的部分,形成测试 用例模版; 比如用例步骤和预期结果,以及正常、异常、以及非功能用例,在用例设计环节作为约束,最大程度上保证用例设计全面,同时结合工具进行用例编写提效,最后通过导入质量管家实现用例在线化。
根据不同的业务,在质量管家上面可以设置不同的模板,在经典实践的基础上,可以附加业务特性,比如资金专项,兼容性专项等,以满足不同的测试需要。
整体的用例编写基于 xmind,适应当前很多测试同学的习惯,同时也比 Word,Excel 的编写效率要高。
用例导入之后可以在线修改,查看和执行,保证用例执行过程留痕。
同时用例还支持一键转回归用例,减少用例用完即抛造成的浪费,有利于测试经验沉淀。
用例设计完成,研发提测之后,就要开始执行。这一部分我们重点关注代码测试覆盖度。
为什么要看代码覆盖度,它可以直观地反映这次测了多少代码,有多少没有被测试,我们知道,代码覆盖 100%,测试可能也不完整,但是代码覆盖面不到 100%,测试肯定不完整。 很长一段时间,我们的测试代码覆盖面是个黑盒,从抽样结果和历史线上问题来看,这部分存在很大的漏洞。
度量代码覆盖技术已经有比较久的历史,以往通常是应用在单元测试等自动化测试环节,用来度量自动化测试的有效性。业界开源的,或者基于开源技术二次开发的技术也比较多。我们采用的同样也是基于 jacoco 的技术二次开发申通自己的覆盖率度量插件,并且基于此配备了一整套的代码覆盖率度量底座(SCC)。
原生的 jacoco 和单元测试配合比较好,如果要应用在更大范围的项目测试上,还存在一些问题需要解决。一些关键的对比如下。(绿色部分为原生支持,红色部分为不支持)
关键问题 1:数据持久化
原生 jacoco 的数据存储在被测服务器上,在云原生的情况下,一旦应用重新构建和部署,相关的数据也随之消失,同时也存在容器重建、迁移等情况导致数据丢失。
由于自动化测试持续的时间比较短,测试中一般不会存在上述情况导致的数据丢失的情况,即便丢失了,重跑自动化测试的成本也比较小(这也是自动化测试的优势)。
但是在人工测试的情况下, 测试周期需要持续数天甚至更长的时间的,而且由于缺陷修复,代码提交等情况,一定会出现被测服务重新部署的情况。数据丢失之后,大多也不能为了跑覆盖率数据进行重复测试,这样数据可靠的持久化就变成了刚需。
这个问题的解决方案如下:
完成覆盖率插件的接入之后,测试人员无需做更多的关注,正常执行测试即可。在测试的过程中, 测试覆盖率的数据持续被生产并且存储在被测服务器上。
当应用重新部署的时候,SCC 集群启动任务,下发命令去对应的被测服务器上 dump 相关的测试覆盖率数据。完成之后在 SCC 上完成覆盖率的聚合计算。
完成覆盖率计算之后, 上传到文件存储服务上,供后续使用。
原生的 jacoco 存在一个比较严重的问题, 就是在多个分支同时部署的时候,在流水线上会合并为 release 分支,本质上 jacoco agent 的计算的是 release 的覆盖率,假如被测分支 feature1 修改了 classA,并且此时测试已经完成了 classA 中的全部测试覆盖,如果此时同样部署在测试环境的分支 featureB,同样往 classA 提交了代码,会导致原本已经测试完成完成的 featureA 中的 classA 的覆盖率被清零。
如下图,其中绿色部分的是测试需要负责的项目,黄色部分是测试未参与的其他分支。
这个问题的影响是,如果被测分支中大量的测试工作已经完成,并且发生冲突的类新增代码(需要重点测试)比较多的话,会导致覆盖率数据会大幅度下降。因此我们也投入大量时间是花了大量的尝试解决此问题。
从 jacoco 的设计来看, 一个类中的一行代码 发生变更,就意味着这个类需要重新测试,有一定的合理性。但是如果按此原则,实际在项目过程中落地存在困难。我们的解决方向是把 “冲突清零” 的范围控制到方法层面,也比较符合实际情况。
技术方案如下:在存储原始覆盖率数据的同时,将需要把覆盖率数据依据方法维度进行切割,在计算的时候,按照方法维度做清零计算,避免整个类的覆盖率被清零。
有了比较可靠的覆盖率统计方案之后,还需要处理一些在实际应用中的问题,才能实际在项目测试中发挥作用,最典型的问题是有些代码通过常规的测试手段无法覆盖,或者没有必要覆盖的情况,对于这些情况,我们在覆盖率报告的基础上,于质量管家中增加了覆盖率分析的节点,要求测试人员对为覆盖的代码进行标注说明,进一步防止漏测情况的发生, 同时对于一些没有测试必要的代码,比如打印日志等,做了排除处理,以免影响最终的测试通过性检查。
报告汇总
未覆盖分析标注
在实际的研发工作中,由于一些客观原因,很多需求其实没有经过测试团队测试验收, 都是由研发自测之后完成上线,很长一段时间,质量部门对研发自测的关注或者是管控的力度较弱,可能是缺乏相关的流程规范,或者是有流程规范,但是落地效果无法把握。
由于上述原因,自测导致的线上问题占比也比较高。
之前的一系列测试能力建设加上覆盖率度量的能力,我们现在有能力科学的对研发自测的质量有一个客观的评估。
整套方案上尽可能做到了对研发过程的少打扰,通过综合单元测试、 自动化测试、以及研发或者产品自测的结果数据,产出融合覆盖率,避免单一维度的测试覆盖率导致的不客观问题。这套方案也帮助开发在无测试帮助的情况下,对自己的发布质量有了一定的底气。
当前覆盖率系统,每天计算产出覆盖率报告 100 份以上。
2023 年的测试任务中,有 8 成以上需求的代码测试覆盖率都超过了 80% 以上, 有效减少了线上问题漏测的情况,而且通过对代码覆盖率的关注,大大提升了测试团队的白盒测试能力,帮助发现业务逻辑漏测,后门代码,无效分支等各种异常。
线上问题中,由于测试不完全导致的线上问题,整体降低 50% 以上。
出于测试成本考虑,覆盖率上我们主要关注增量覆盖率,这样就有另外一个问题没有解决, 一些增量的代码对原有代码的影响, 其实是没有能力反映出来的。这类问题也是线上比较常见,但也是比较难以通过测试发现的。这需要我们思考其他的解决方案。
3.2 思路
常规的思路一般有两种:
1.通过投入大量时间做全面的回归,大水漫灌式的做法, 我们也尝试过类似的实践,这样的方案对于产品规模较小的业务比较合适,比如功能比较单一的 APP,但是如果涉及到一个全链路的复杂系统,这样的回归方案变得很难落地。
2.通过自动化的方式,尽量覆盖更多的代码逻辑,通过高效的自动化方式进行回归。 但是在实际的生产中,自动化用例往往不太成熟,无法实现有效地回归覆盖。当然有些公司的自动化建设比较成熟,但是带来的其他问题是自动化的维护成本和运行成本高企。
上述两种方式在申通不同的业务上也有实践,在此的基础上,我们产出了另外一套精准测试的方案,期望能够准确识别变更的影响面,做到回归测试有的放矢,给人工回归,和自动化回归提供有效支撑。
要达到这一目标,解决方案中至少要具备两部分能力:
1.识别影响: 基于变更识别关联影响(影响了什么?)
2.测试评估: 基于影响识别测试范围(测什么?)
对于第一部分,以最常见的业务逻辑代码变更举例,常规有两种做法:
动态方法: 采取如 java 字节码技术,通过动态收集代码运行时的调用关系,建立代码之间的调用链,这样的方法好处是只要采集的数据足够全,得到的调用链相对比较真实,可以屏蔽一些无效的调用,产出的都是真实发生的调用关系,缺点就是技术成本高,包括接入、采集、存储和计算。
静态方法: 业界有一些开源的解决方案,如 java-callgraph,这样的技术好处是技术成本较低,能够通过静态扫描的方式,短时间内得到代码之间的调用关系,缺点是给出的数据颗粒度往往比较粗,需要对开源包进行二次开发来真正达到准确识别变更所影响的调用入口的效果。
基于综合成本考虑,申通采用静态方法,结合开源能力做了二次开发解决了在插件部署的效率问题,以及结合研发流水线实现影响面自动测算,我们可以通过对被测代码进行静态扫描,产出一次变更所关联的所有入口。
通过对变更的分析,得到被影响的代码入口,也基本得到了测试入口。
第二部分,基于影响识别测试范围。这一部分的解决方案需要提前建立测试用例和代码之间的关联关系,并准备一套 “录制系统”,基于录制系统,测试人员一条条的执行用例,系统后台记录在用例执行过程中所经过的代码,以此来建立用例和代码之间的关系。这样的做法可以建立比较精准的关联关系,不足是维护的成本巨大,以申通举例, 上万条测试用例,一条条的通过录制回放的关系,会耗费大量的时间,等一轮关系建立完成之后,可能系统又发生了较大的变化,导致所有的关系都要重来。在目前的情况下,这样的成本无法接受。
我们需要解决成本的问题, 经过思考,我们决定仅从测试用例入手建立到服务层的关系,这样可以避免底层业务逻辑代码变更带来的关系维护成本,同时在建立关系的时候也可以比较快速地完成。
方案示意如下
对于人工测试:
1.对页面测试的人工用例(用例集合),我们先通过手工绑定的方式,建立人工用例和页面的关系。
2.然后在对应的页面中,通过抓包的方式,全量的抓取该页面所调用的后台服务。
3.然后反过来,我们就得到了用例 + 页面 +API 之间的关系图谱。
对于自动化用例:
相对来说比较容易,通过静态分析的方式,可以自动得出自动化用例所调用的后台服务。也就建立了自动化用例 +API 的关系图谱。
完成结合:
第一部分的能力,使得我们通过变更的代码,可以得到影响的 API, 通过影响的 API 可以得到相关的用例,包含自动化和手工部分。
在实际的测试过程中,测试人员只需要输入对应分支,后台就可以推荐出对应用例,实现精准的范围评估和回归测试。
由于识别影响和测试评估的能力是互相独立的, 严格来说,识别能力作为能力单独对外提供服务。
比如识别到某次变更影响的入口之后,发现相关的入口没有自动化用例或者人工用例后,可以自动生产待办任务,督促相关测试完成用例补充。
又比如在 Code Review 的环节,可以提供更多的输入给代码审核人,做更加全面的代码审查等等。
还有一些定制化的应用,比如某些接口协议发生变更的情况下,可能要触发对应的前端或者依赖方的回归测试。
基于这些能力,可以进一步的提升测试的完整性。
申通技术部现在有上万的自动化用例每天在运行,自动化的应用渗透在从提测到线上巡检的各个环节。如何保证这些自动化用例能发挥效果,是一个需要关注的问题。
常规的做法是,从手工的用例出发选取一些关键的业务用例进行自动化实现,申通也有类似实践,相比有些公司有专门的自动化测试工程师来负责此工作,在申通并没有如此细致的分工,同时手工用例编写的细致程度,以及选取要实现自动化的用例存在很大成分的人为判断因素,导致在所谓核心用例的覆盖面的基础上,无法提供更多有效的支撑来进行自动化用例建设。我们的做法除了从用例出发,还会从以下几个方面关注。
顾名思义,就是从应用接口看那些需要实现自动化的服务。第一个要解决的问题是接口列表从哪里来,与一些大厂可能有比较完整规范的接口文档,服务注册机制来识别服务列表不同,在现有的条件下,申通是通过线上监控的数据来拉取服务列表。
从监控平台拉取的数据除了有服务地址之外,还有服务调用量等信息,通过这些信息,结合业务重要性的综合判断,我们在全量的服务列表基础上可以对服务进行分级,那么接口自动化目标也就有了优先级,基于这些优先级去进行服务自动化覆盖就变得有的放矢。
上文提到了我们有一套代码覆盖率度量的基础能力(SCC), 基于此能力,我们可以衡量出在自动化集中运行期间的代码全量覆盖率,借此可以对自动化的有效性和接口覆盖的层面上更加深入。
场景是什么,往往是基于业务的描述,在测试方面可以抽象为外部调用进入到系统的不同入参组合,以及这些入参所经过代码调用链路,那么基本可以认为场景是入参决定的;简化来说,我们可以认为是经典的 “等价类”,“边界值” 的实际生产应用。不同于 “设计” 各种测试用例, 在场景覆盖的命题下,我们更多是通过获取线上数据来进行有效的组合形成不同的自动化场景用例,所谓 “有效组合”,是指的我们基本不会去臆造一些实际生产中不存在的数据,这样的做法更加贴近实际,同时也保障技术资源不会被浪费。
对于场景自动化的落地,大厂的做法可能是通过引流或者是链路分析来识别,需要较大的数据存储和计算成本,申通的做法成本更低一些, 通过线上数据完成各种场景的组装之后,将被测系统的业务抽象为流程(相对于单接口)自动化用例,通过类似数据驱动的方式,输入到流程用例中,完成业务系统的场景自动化覆盖。
上述的做法都是问题事前的方法, 对于事后的方法判断测试有效性主要是通过线上问题的反馈来回溯验证,这部分下文展开。
申通技术团队,已经完成了将原 “技术支持” 团队升级为 “技术服务” 团队,在更加规范化地给用户处理产品使用上的问题过程中,积累了大量的服务数据,其中一部分就是产品线上问题和建议。
现在技术服务团队基本上涵盖了申通技术部提供的所有产品和系统,相关的线上问题都可以得到比较及时和完整的收集。 基于这些线上缺陷,技术团队可以回过头去看在研发测试过程中,出现了哪些遗漏,比如是否测覆盖率不足,是否回归不全面等等。研发,测试,上线,服务四个环节有机协同,给系统优化提供方向,形成 PDCA 良性循环。
技术服务补全了从研发到上线的质量体系
上述的一系列措施,便是现在申通测试如何回答 “测完了没?” 这个问题的答案,当然测试的范围非常广,我们只是基于现阶段的业务特点和技术质量要求,把重点放在了功能测试层面,对于非功能测试领域的关注还是相对较少。
对于未来我们计划在下面的几个方面进行一些探索。
对于覆盖率,目前我们能识别后端代码的测试覆盖率,对于前端代码的测试覆盖率,需要和前端研发团队深入合作,制定标准,通过埋点等定制化的技术方案来实现。
对于精准测试,目前我们的关注点放在了后端代码的变更上,未来还可以通过识别前端代码变更,数据结构变更,配置变更的影响来扩展更多的应用场景和访问,使得质量风险的评估更加全面。
AI 的应用,尤其是生成式的大模型,在各领域都有不错的实践,在技术方面,之前我们有看到 AI 编程,AI 做代码扫描的实践,对实际的工作也是非常有帮助,在质量领域方面,AI 至少可以在测试分析,自动化测试,代码缺陷分析方面做一些有效的辅助。
基于现在的 “小模型” 技术,我们可以用更低成本对模型进行部署和训练,基于申通现有的质量要求以及历史上的质量数据,训练出更加贴近申通业务的质量 AI。
更多内容可以学习《测试工程师 Python 工具开发实战》书籍、《大话性能测试 JMeter 实战》书籍