【NCTS峰会回顾】汽车之家闻小龙:QA团队精准测试实践之路
2019年10月26日,由Testin主办的第二届NCTS中国云测试行业峰会在京召开,此次峰会以“AI+未来”为主题,汇聚来自国内外测试领域的知名专家学者、领先企业决策者、高层技术管理者、媒体从业者等,共同探讨高端云测试技术,帮助测试从业者了解最前沿行业趋势,及最新的行业实践。
会上,汽车之家新车电商事业部高级测试工程师闻小龙做《QA团队精准测试实践之路》主题演讲。闻小龙指出,“自动化测试本质上是根据测试的设计去执行的,只是执行手段不一样,当设计出了问题,自动化测试不管再怎么执行还是会遗漏那个问题,所以,自动化测试没有解决所有的问题。”
以下为闻小龙演讲实录:
我是汽车之家电商事业部的闻小龙,今天很高兴给大家进行分享,我想先做一个现场调查,在座各位多数工作是功能测试相关的?还是很多的!我也是一名一线测试人员,所以我今天讲的内容可能大家会比较感同身受。
我们组内每隔一段时间聚在一起聊聊最近工作的问题和可以改进的地方,时间长了,我们发现问题是多种多样的,但抽象出来后最主要的矛盾大概有那么两个。
一、测试深度、广度,与测试资源、项目时间的矛盾。
二、被测系统的黑盒状态与需要了解系统的矛盾。
发现矛盾以后我们进行了思考,下面我想说一下整个思考的过程,并且这个过程我想通过一个比较有意思的方式去跟大家交流。
前一段时间我发现一个游戏,这个游戏在朋友圈流行过一段时间,相信很多人也都玩过,规则很简单,你需要在规定时间内找出跟周围色块不一样的色块,左边图是第一关的截图,大家很容易看到右下角色块会浅一些,点击它,这关就成功了。当玩儿到55关的时候,大家还能一眼找到不同的色块吗?
还是有一些难度的是吧,所以我觉得我死在这关不是特别冤,当色块不断增加你会发现难度是会递增的,我觉得这个东西特别像我们的测试,为什么这样说呢?大家都写过用例,用例设计是把不同逻辑分支进行组合,最后会罗列出来数量比较大逻辑路径,测试工作的本质是什么?我们对所有路径进行覆盖,然后找出其中有问题的几个路径,举个例子,比如:订单系统的测试,订单类型其实就有很多,再加上不同平台下PC端,APP端;还需要考虑中间的操作差异,比如说我加到购物车里下单,还是直接下单,考虑完这些影响因素以后,按照我们用例设计方法正交分析法进行分析,很简单的一个下单场景,我发现有102种路径组合。
假如说我们订单系统修改了其中一个路径,要完整回归102种下单情况,才能覆盖所有的逻辑。我们发现这个成本在我们敏捷开发和快速迭代当中是一个很大的瓶颈。
我们要去解决这个问题,第一个想到的就是减少工作量,还是从游戏说起,既然这一关难以完成,并且我们分析了造成难度的是色块数量过多,我们有没有什么方法把色块减少呢?如果我们能把色块减少是不是意味着回到了前面的关卡,所以你就能快速的把问题色块找出来。映射到测试,每个团队都有经验丰富的员工,会根据这次改动告诉你其实有一些订单类型是不用回归的,因为这些跟你的改动点关系比较弱,所以说我们不用测;然后会告诉你这个逻辑的改动点,和他是否加入购物车其实感觉上关系不大,所以直接测直接下单就可以包含所有场景。
当我们把经验者建议的影响因素刨除后,我们发现场景确实大大减少了,但是长时间按照这种思路实施以后,你会发现你还是偶尔的遇到一些线上的问题,你拿出来复盘分析的时候会发现你排除掉的路径出了问题。为什么会出现这个问题?分析到最后发现是因为这种经验性的进行测试范围的缩减是没有依据的,当你把你的可能发现问题的路径排除在计划之外的时候,不管执行做的再仔细,其实在计划阶段你已经失去了发现它的可能性。
我们测试人员真的没有办法搞定这个问题么?这时候就有人会说既然质量保证是我们天职,那我们还是需要投入足够的资源和时间把所有路径都回归完,起码能够保证系统的稳定性。但是说这句话的同学忽略了一个问题,游戏的右上角是有时间的,游戏规则不允许你随意的拉长单局时间,就像你老板跑过来质问你,为什么我给了你这么多人,却没有达到快速迭代的效果一样。
这时候我们就想,难道我们就没有一个好方法了吗?当然不是。依据大家玩游戏的经验,你想快速而显著的提高游戏能力,其实很简单,那就是充钱买道具。现在大家看到的就是我们的第一个道具:自动化测试,这也是我们的第一个想法,既然你工作量过大,那我也不去想怎么减少你的工作量,我用一个更直接粗暴的办法,使用工具遍历所有路径,大家都知道自动化测试执行成本是不高的对吧,它能在瞬间完成海量的路径覆盖,这是一个优点。但是自动化测试真的一劳永逸么?从很久之前行业就存在了很多的自动化解决方案,当然我们也不能免俗,不管是UI、API的我们都做了大量尝试,但是真的实践过程当中我们发现自动化这个道具还是存在一些问题的。
自动化测试并不是凭空而生,每一次需求进行到开发提测阶段,进行脚本编写的时候其实是一个很大的成本。有的同学会说,脚本编写是一次性成本,可能第二次回归时候你去用它就没有这部分的成本了,但是你真的在做时候就会发现,当项目进行快速迭代时,第一个版本写好的脚本往往到了下一个版本就跑不通了,所以又需要你去付出一些维护成本。
自动化测试还有一个问题,它在一些技术栈当中不能保证所有地方都能覆盖到,有一些不能覆盖的地方,我们通常会在自动化测试测后增加手工检查,因为人的灵活性还是有巨大优势的。但是这里有一个问题,自动化测试跑完以后移交给手工人员,但当功能总量很大的时候,你发现你已经无法特别精准的描述你需要补充的地方了,所以手工测试人员就不能有的放矢,被迫按照自己的方式做尽量多的覆盖,这样会在测试过程中重新引入主观因素,最后无法避免的造成漏测。
另一方面,还记得前面咱们说过的盲目测试范围缩减会造成漏测的问题么,其实自动化测试也会遇到这个问题,因自动化测试本质上是根据测试的设计去执行的,只是执行手段不一样,当设计出了问题,自动化测试不管再怎么执行还是会遗漏那个问题,所以,自动化测试并没有解决所有的问题。
我们发现,原来自动化测试有这么多不尽如人意的地方,这个道具有一些缺陷,那我们是不是该寻找一些新的道具呢?我们先不揭晓有哪些新道具,我们分析一下我们到底需要些什么?
还记得我们开始说的两个主要矛盾么,第一个矛盾其实就是工作量与资源时间的矛盾,那我们最需要一个工具去指引我们缩小工作量,但是这个缩小要是有依据的,我们需要的这个范围,一定是可以找出所有问题的,这是我们想要的第一个道具。
如果还可以拥有一个道具,我们可以顺着后面自动化的思路来想,自动化是一定要做的,但是自动化中主要的问题需要第二个道具来弥补,这个工具可以告诉我们一个自动化明确的边界,它可以是一个可视化显示,告诉手工人员需要对哪些东西进行补测。
我们既然有了这些愿景,我们就进行了一些调查和思考,我们发现,第一个缩小测试范围的功能需要在代码维度入手,通过开发每次提交的代码差异去分析我们需要测试的范围,我们拿到代码变动去分析测试范围,这就把分析过程做到了有据可依,这也解决了我们开头提到的对系统了解不足的矛盾。
第二个工具是对测试过程进行可视化反馈,这个东西就是代码覆盖率监控,不管是自动化测试还是手工测试,最后都会得到一个可视化的测试报告,就是哪些逻辑跑过了,哪些逻辑还没有跑。
有了这两个理论基础后,我们查找可复用的技术栈时发现已经有了一些比较好的开源工具,我们将这些开源软件进行二次开发,加上自研的DIFF引擎,可以达到我们的预期想法,我们依据这些思路对系统进行了实现
下面这一部分是工具在我们项目当中跑起来以后,过程中的一些实践和收益情况,下面我们大家一起看一下。
首先简单说一下实现思路和使用的技术栈,首先我们代码管理托管在私有的gitlab上,首先我们通过Git Diff命令获取代码差异,当开发修改了代码提交版本后,我们用这个版本和上一个版本进行DIFF,差异信息会告诉你某一段代码哪几行发生了改变。我们通过一些字符处理方式,把这个处理成通用的Json格式的数据,格式大致为某一个JAVA类中哪些行发生了改变。我们分析数据发现这不是我们想要的。因为行没有很好的含义性,我们发现方法是很好的入口,因为一个方法是一个动作,我们其实想知道的是哪些动作发生了变化,然后进行针对性测试。所以我们需要对它进行转化,在转化的过程中用到了AST(抽象语法树),抽象语法树的简称是AST,这是一个可以把静态代码实例化成树状结构的工具,这个树状结构的每一个节点都是一个语法结构,方法,行,甚至注解都是语法树的一个节点。都可以方便的进行分析和处理,我们通过语法树可以拿到每一个方法行范围,大家知道上一步已经拿到了差异行号,我们用差异行号去命中所有的方法,当一个行号命中了一个方法,我们把这个方法进行标记,最后我们得到了差异的方法列表,方法是动作,因此,我们知道了哪些动作发生了代码变动,我们需要对这些动作进行覆盖。
看架构图中的红线,是方法的差异列表,我们把数据引出来,然后我们灌到覆盖率监控里,就会得到基于代码变动的覆盖率监控报告。
Jacoco大家如果关注过覆盖率监控,应该都听说过,是JAVA语言里的一个覆盖率监控开源工具,我们对源码进行了二次开发,并与代码差异分析数据结合,下面讲述这两者是如何进行融合的。
首先我们需要简单说一下Jacoco的工作原理,首先Jacoco分为两部分,第一部分是插桩部分,对被测服务进行插装操作,其实就是在每一个逻辑分支插一个探针,当逻辑分支被执行了,探针会变成True,未被执行会保持初始的fals,最后通过探针数据我们就能得到哪些逻辑被执行过了,而哪些逻辑未被执行,
下面我们来说Jacoco的第二部分,命令部分,我们可以通过一个jacoco的dump命令远程获取这些探针数据。
第二个是merge命令,其实是将多次拿到的探针数据进行合并操作,这个解决了什么问题呢?举个例子当被测服务多次重启,但是重启前做的覆盖数据如果不想丢掉,就需要将它合并到一起,这样能够保证探针数据的完整性,当你拿到完整覆盖率数据以后,最后一步是report命令,这个命令是把覆盖率数据形成一份可视化报告,告诉你整个的项目有多少被覆盖。
接下来就是代码差异信息的引入,我们入手点是在可视化的时候做一个过滤,当执行report命令时,会有一个遍历所有方法生成报告的逻辑,我们会在这个遍历的过程中判断当前方法是否发生改变,如果没有改变我们就剔除掉,最后得到的就是我们需要测试部分的报告了。
我们的本意是想通过工具帮助测试人员更好完成测试工作,但是我们不想增加额外成本,所以我们把上面部分功能进行模块化拆分,每一步可能是一个命令,一个接口,可以嵌入到CI的过程当中,我们开始嵌入到Jenkins,后来又对接了集团的云平台,因为我们将功能进行了服务化,可以很方便的和外部系统进行对接。
下面跟大家交流一下工具在我们测试工作当中的一些应用情况,下面是一个示例,某天下午我收到的开发的提测,他告诉我这个需求已经部署到测试环境,我可以进行测试了。
我们先来说说这个需求的背景,我们是做汽车电商的部门,基本业务形态是可以在网上买一些汽车相关的产品,比如买一个汽车的抵扣券,你花100块钱买一个2千块钱抵扣券,你出示券码可以在总车款抵扣两千块钱,就像之前团购的餐券线上购买线下消费。
但是汽车电商略有不同,因为它还需要支付大额的尾款,比如说支付20万尾款,这个支付过程时间有可能会比较长,比如有些顾客需要刷好几张卡,如果其中一个卡出问题还需要解决一下,所以支付系统给我们提了一个需求,需要在他刷第一笔款时把券码锁住,避免打款过程中,券码状态发生改变。
这是他大体的需求,从技术维度来讲需要我们提供一个接口,我们对接口进行测试,按照常规测试,与开发聊很多开发的设计和我们需要测试的东西,既然是要试用新工具,那我决定这次换一个方式开始这个任务,我没有找开发直接聊,而是拉了项目覆盖率报告,我们发现都是红色的,这代表什么?代表着这个分支没有被测试过,因为还没有进行测试。
第二个现象是这个项目的覆盖率报告只有一个类被显示了出来,这说明开发只修改了这一个类,所以我的测试范围就被控制在了这一个类以内,这就达到了缩小测试范围的目的。然后我们再去分析改的这部分代码,我们发现这就是是一个spring MVC编写的接口的代码,前面是各种参数校验,后面是对券码的操作逻辑。
我们先进行冒烟测试,就是拿开发给我们一个URL 和demo参数进行调用,我们将接口测试的数据录入我们自研的接口测试平台并运行,发现接口给我反馈了错误代码4207(提车码不正确)-不可冻结。按照之前的方法我们会直接扔给开发让他们查找原因,现在我们换了一个方式,我们没有直接扔给开发,而是拉了一次覆盖率报告,报告中绿色代表的是被执行,黄色代表部分被执行。我们发现逻辑已经进入了这个方法,然后在第三个IF判断时候进入了报错逻辑,并抛回了错误信息,这个过程有一点像开发的Debug,我们看语句块进入逻辑,发现是券码状态有问题,我推断可能开发给我测试数据时候,可能已经用这个参数进行了自测,已经变成冻结状态了,我再次冻结自然可能就有问题了,我分析出问题的原因,其实问题已经解决一大半了,怎么解决呢,因为我们还有解冻接口,我用同样的参数调用了解冻接口,我发现成功了,说明我们的推断是正确的。
我们继续验证,我再次调用冻结接口,我发现这次冻结成功了,发现我们的推断是完全正确的。
我们再拉一下覆盖率数据,我们发现已经跳出了上一次把我们扔出去的逻辑,然后进行到底部也就是主逻辑,我们发现这个方法完全被执行了,这个节点在测试中其实很重要,叫做主流程跑通。有些质量要求级别低的项目主流程跑通是可以上线的。
但是我们这时候拉取了整个项目覆盖率情况,我们发现只有62%,我主流程跑通了,但是覆盖率只有一半多一点,这个数据当然不是很理想,没有关系,我们进一步去分析原因,我们需要分析的是里面的红色部分,我们发现红色部分都是异常情况的判断,说第一个校验的是参数合法性,这段逻辑无法进入是因为我们参数合法的,我要进去很简单,我将参数改成非法就可以了,所以我把其中一个参数APPID改为不合法,然后再去调用,我发现有一些不同,给我返回了一个类似Json的信息,但是内部是空白的,这应该是有问题的,因为一个接口可以返回正确也可以返回错误,但是返回空白一定是不正确的,所以我就把问题反馈给开发,开发直接问我哪一个方法你知道吗,我直接把方法贴给对方,因为我跟进覆盖率报告,分析的就是代码级别的东西,所以他根据你贴的代码,就不需要调试直接就定位到问题了,然后修复了该问题。
我们再次用同样的参数组合调用了一下冻结接口,发现反馈给了我们想要的东西,同时开发也表达了他比较激动的心情。为什么会这样?因为你给他减少了工作量,他不需要去调试,不需要重复做你做过的事情了,所以代码级别的一个沟通就会给他省掉很多工作量。
我们如法炮制,把所有代码块都进行了条件分析,对它进行进入测试。
这个校验的是提车码不存在的情况,我们把提车码改成不存在的场景也进入了。这个校验的是提车码的有效期,我们把时间改成过期也可以进入这个逻辑。
其实操作都差不多,就是根据它的条件进入反面的逻辑就能进入到相关逻辑,这是一个信息的状态,这是订单类型,我们改成非法类型也可以进入,非法校验确实很多。当我们把所有非法校验进行了一个测试以后,我们这时候拉取覆盖率报告,发现基本上全变成绿色了,因为所有逻辑我们都进行了覆盖,下面仅有两行红色,我们发现其实是一个异常捕获,就是当你逻辑进行不可预知异常才会进入,这是一个破坏性测试,比如中间掉一个接口报错了,然后我们就会进入这个异常捕获,理论上也可以进入的。
这时候我们再拉一次项目级别覆盖率数据,我们发现自解码已经到97%,分支已经到83%,我们认为这还是不错的覆盖率数据,并且红色部分我们也进行了合理化解释,所以我们觉得这是比较理想的测试结果。
我们复盘一下这个过程,这个工具到底帮助我们提升了什么?首先我前面故意找了一个我不是很熟悉的隔壁组项目来做的。开发只给了我一个URL和Demo参数,我发现我最后得到了一份很丰富的测试用例,我跟测试人员聊了,其实真的是按照传统方式测的话可能其中很多的前置条件还会漏掉,一些异常情况会进入不了。
所以第一个收益是在我不了解一个业务逻辑情况下我完成了测试,并且这个测试是相对全面的。
第二个收益是我在前面测试所有参数组合都录入到我们系统当中,当我需要回归这个接口的时候可能只需要运行上面录得所有接口参数组合就可以了,但是这里我想说的并不是自动化测试,当你多次迭代后,如果你自动化测试没有进行及时的更新,那自动化测试的效果就会越来越差,这时你就会发现自动化的测试的作用会被逐渐消磨掉,我们发现覆盖率监控可以解决这个问题,我们每次执行自动化测试都会关注它的覆盖率。假如说这一次是98%,下次执行是我发现覆盖率变成了60%,一定是开发加了新逻辑,而你的自动化测试没有更新,那就需要你对自动化用例进行更新了,所以等于我们加入了一个自动化测试用例效果的监控机制。
再说第三个收益,我们在整个用例设计过程当中是有依据的,我们不会随意的删减有效用例,也不会做一些多余的无效覆盖,我们把这叫做精准化测试思想,大家可以看到精准化测试思路在我们测试流程当中,对我们的帮助是多方面的。
再来说说我们的愿景,我们更深层次的复盘了我们的整个流程,发现中间这个工具只是帮助我们去分析了一些东西,但是具体分析和操作还是需要人力去完成的,我们就在想其中一些工作量是不是还可以继续交给工具。我们既然拿到操作覆盖率,我们反过来想我们是不是能够建立代码节点与用例集、用例组的关系,就是当代码改变时候我可以映射出我需要执行的哪些用例。当我们采集到这个关系以后,我们用DIFF引擎把差异代码分析出来,我们直接映射到的就是我们用例级别的测试方案了。