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