本文作者:程胜聪 - CODING 测试域产品总监
在业内如火如荼的 DevOps 转型过程中,自动化测试始终是热点之一,毕竟提供快速质量反馈是达成 DevOps 目标的关键。于是,作为测试领域的“皇冠”,自动化测试的落地实施始终为人们所关注。但是落地当中产生了种种问题甚至是争论,经久不衰,无形中给自动化测试体系建设蒙上了层层迷雾,让人疑惑。下面我们就一些踩过的“坑”进行探讨,期望这些经验分享能够有助于揭开迷雾、看清方向。
要不要做自动化测试?
在软件工程理论当中,测试相对开发来说是个“辅助”的角色,而软件交付的产物也不必包括测试的产出。随着软件研发过程的复杂度提升,虽然还是辅助角色,而且其价值仍然难以单独衡量,但是测试对质量保障的作用已经深入人心,以至于从 10 多年前开始,已经没有人会挑战测试作为研发体系中基本角色的存在了。
然而类似的,作为“辅助的辅助”,测试领域中的自动化测试又开始面临着同样的质疑:自动化测试到底有没有价值?这个问题在 10 多年前开始被频繁提出,而且难以回答。作为处于辅助地位的投入,人们肯定会密切关注其成本大小以及跟收益的对比。自动化测试最开始出现,是为了替代重复性的手工操作,从而节约回归测试的人力成本,于是要获取正向收益的前提是执行的次数够多: 自动化收益 = 迭代次数 x(手工执行成本 – 用例维护成本)- 用例编写成本
所以,在 DevOps 时代的频繁发布测试场景下,自动化测试的价值得到了充分展现。要不要做自动化测试的问题如今已经不会造成困扰,因为当下业内已经形成了一致的认识:自动化测试是持续测试的基础,是 DevOps 时代中不可或缺的实践。此外,由于自动化测试的执行效率很高,体现出来的时间成本优势更是明显,甚至比成本优势更能戳中这个快速发布时代的痛点:因为不这样做的话,我们会越来越难以应对短周期发布所需要的快速有效验证。
有策略地开展自动化测试
测试体系建设需要分层策略指引,接口测试往往最优先
罗马不是一天建成的,为了达成自动化的目标,进行自动化测试体系的建设是需要投入资源和人力的。因而在具体落地过程中,我们需要充分考虑 ROI,来设计符合实际情况的目标达成路径。自动化测试确实有很大价值,但不代表我们应该无节制地投入到各种类型的自动化测试当中:自动化测试是为了验证既定逻辑是否符合预期,在需求变更频繁的场景下,自动化代码的维护成本不可小觑。所以我们需要合适的策略,来指引自动化测试的实施——金字塔模型。
不少人对金字塔模型的第一印象,是其给出在 3 种测试上的投入占比建议:单元测试最多、接口测试居中、UI 测试最少,比如 70%、20%、10%。但更为重要的是,Mike Cohn 提出了对测试进行分层的理念,以及给出了每个层级的测试优缺点:越接近用户使用界面的高层次测试,粒度越粗,效率越低,写的测试应该越少;反之越接近底层代码的测试,粒度越细,效率越高,应该写的更多,也应该执行得更频繁。
而在实践当中,每个企业面临的场景不同,投入情况也不一样。比如现实情况可能并不是金字塔而是纺锤形状的,中间的接口测试占比最高。种种实践表明:在自动化测试建设的初期,接口测试往往是团队开展自动化测试的首选。这是因为接口测试兼备执行效率和体现业务价值两方面的优点,在这个领域进行资源投入较为容易被技术团队和业务团队共同接受。而且由于接口定义的稳定性也较高,其维护成本也是可控的。所以相对单元测试和 UI 测试来说,接口测试的投入产出比可以说是最高的。
接口测试以接口定义管理为基础,契约变更的同步提醒和 Mock 是关键
接口测试通过调用接口来达成测试验证的目标,既包括系统与系统之间的接口,又包括同一系统内部各个子模块之间的接口。接口测试的重点是检查系统/模块之间的逻辑依赖关系,以及进行交互的数据传递的准确性。接口测试是黑盒测试的一种,却是最接近白盒测试的黑盒测试,故而在较早发现缺陷和执行效率上也接近于单元测试,往往被称为“灰盒测试”。
接口测试的用例一般包括单接口用例和基于业务场景把不同接口集成到一起的多接口用例。单接口用例是基础,而且也是开发调试过程所需。业内比较流行的是用 Swagger 进行接口文档管理,Swagger 预定义了主流编程语言相关的代码注解,可以在接口实现代码变动之后获取接口文档的更新,自动反映接口变更的功能对自动化用例的维护来说非常重要。
多接口用例则是测试人员根据对需求功能的理解所设计出来的,这部分用例就充分展示了自动化测试的业务价值。由于这部分用例相对复杂,团队会需要为之准备基础框架,甚至打造脚手架来提高编写效率。在实现上一般会包括接口规范定义、接口间调用的代码管理、测试数据的存储管理、执行调度平台、结构化统计报告这几个部分的能力。于是在业内也出现了不少在这个领域的效率工具,比如 Postman、ReadyAPI!、Robot Framework,以及“低代码”的平台 apifox、Eolinker 等。
有了接口的契约定义,就可以对未上线的接口实现 Mock(测试替身或者挡板),这样就可以不用依赖于具体的开发实现而构建场景测试用例,有利于测试开发之间或者不同开发者之间的并行协作。现在搭建 Mock 解决已经有很成熟的框架,比如 Mockito、EasyMock 等,或者平台型工具 postman、apifox 都能够很方便的搭建 Mock Server。
总的来说,有了可以遵循的接口定义规范、加上接口变更的信息同步、以及提供对接口的 Mock 服务,在团队中就可以基于 API-first 的方式实现并行开发、调试和测试了。
高效编写自动化用例 ,有没有“捷径”可走?
现今系统的功能越来越强大,也越来越复杂,写好自动化测试用例不是简单的事情,这一块也是不可忽视的投入。于是在自动化建设的实践中,我们自然而然会追求更高效的方法。追求高效之路没有问题,只是如何才能避免走歪呢?下面我们对一些常见的“提效工具”进行探讨,期望可以对现实的实践起到借鉴作用。
编码方式向左,低代码平台向右?
现今系统的接口往往错综复杂,要做好接口测试,既需要对业务层面的系统/模块之间的逻辑关系有深刻理解,又需要掌握好技术层面的各种测试框架和相关编码技术。可以说,接口测试自动化的建设仍然面临着较大挑战,人们自然会寻求高效的方式进行实施,相应的就出现了对自动化用例低代码平台的需求。于是出于“对测试同学技术能力的现实考虑”,研发 Leader 往往会“以负责任的态度”,寻求“入坑”去追求低代码平台。低代码的概念在近两年如此之火,更是导致这个话题反复被提起。那么我们在写自动化测试用例的时候,到底应该是遵循老老实实写代码的方式,还是应该大胆采用低代码平台去快速提升覆盖率呢?
低代码平台的优势是什么——那就是通过降低自动化编写的技术门槛,让编码能力较弱的人员也能参与进来,从而较快地从 0 开始提升自动化覆盖率。一般低代码平台都是基于接口测试自动化的数据和代码分离的原则而进行设计的:先把常用的接口操作方法抽象出来,并且封装好(在 Robot Framework 中称为关键字),然后通过在界面上拖拽组件组合成一个逻辑流程,再加上数据传递驱动形成一个完整的业务场景。所以使用低代码平台给人的体验,就是通过表格表单的操作实现自动化用例,感觉上“不需要编程能力”。
然而,在实践中使用低代码平台进行复杂业务测试时,对数据字典、操作的抽象(关键字)、设计能力的要求还是很高的,不然碰到相对底层逻辑的变更,那就需要改动茫茫多的用例。从不少团队的实践来看,低代码平台在中长期维护上面对多变业务场景会显得难以为继:缺乏技术抽象思维的、非工程背景的业务(或者业务测试)成员很难持续“重构”现有的用例。而为了保证用例的准确性和整体运行效率,对自动化用例的重构是不可避免的。面对着一系列的“表格式”文档,工程师普遍认为这种方式是脆弱和低效的,从而不愿意接手。
所以,是否使用低代码平台取决于我们对业务发展的预期:对于非核心业务,如果预期模块是相对固化而不需要演进的,那么不妨大胆利用低代码平台开展快速覆盖的自动化测试,低代码平台确实缓解了团队管理者的“自动化建设焦虑”,帮助团队迈出自动化测试的第一步;而对于核心业务、注定要跟随着业务发展而进行技术演进的模块,那么就需要充分考虑中长期达到提升自动化编写效率的目的。原则上我们应当承认,自动化测试用例的编写,事实上存在着“工程门槛”,也确实是需要“工程门槛”的。哪怕使用低代码平台,也需要拥有“工程师思维”,考虑“关键字”和数据结构的封装和维护。总的来说,应该通过增加可视化、减少重复性工作的方式,让工程师更加高效地编写自动化用例;而不是“无限制的降低门槛”,以期望未经训练就可以写出健壮的、高质量的自动化测试用例。
流量录制回放方式,可以取代手工写自动化用例吗?
关于接口测试,现实中还存在一种情况:那就是对现有系统缺失的接口测试进行“补锅”,需要对已存在的众多接口场景进行测试用例的补充覆盖。如果采用传统的测试方式,是从接口定义开始,手动梳理系统接口文档、对照着录入测试用例、写好逻辑断言,然后还要在深入理解接口后,构造相应的测试数据,这是常规的方式,也是正确的方式,但是毫无疑问是个慢工细活。当我们需要在较短时间内完成一轮补充的话,这个方式显然做不到。于是一种新的解决方案出现:自动采集真实的流量并形成接口测试用例。
流量“录制”指的是对线上的流量请求和返回进行拦截录制,然后记录下来形成测试用例;而“回放”指的是把线上录制下来的请求和返回,复制到一个准生产环境服务中,测试新功能和服务是否满足要求。真实(数据)和高效,是流量录制回放方式的两大优点,而其主要的应用场景包括:
1. 出现线上故障时,录制的真实流量可以回放到开发/测试环境来进行调试分析,这是用到“真实”的场景,也是流量录制回放功能的基础价值;
2. 对录制好的真实流量进行复制放大、应用到预发布环境中作为压测用例,这也是用到“真实”的场景,真实流量对压测来说确实是个很好的补充;
3. 如上文提及,批量形成第一次的回归自动化用例集合,这是用到“高效”的场景。
实现流量引流录制的主流方式包括:Nginx 的镜像复制、GoReplay 直接监听网络接口捕获 Http 流量、基于日志解析,以及针对应用业务自研复制引流功能。业内最为流行的工具当属 GoReplay 和 TCPCopy,其中 GoReplay 尤其简单易用,而且无代码入侵,对线上应用的影响可以忽略不计。
那么是不是有了流量录制回放的功能,就不再需要手工写自动化用例了呢?肯定不是的,我们还需要踏踏实实写自动化用例。首先,流量录制回放是后置的“补锅行为”,没有人希望等到接口上线之后才去测试,所以往往是一次性的工作;其次,流量录制也是需要较长时间才能达到较高的用例覆盖,对于非常用的边界,但是重要的场景我们仍然需要人工去设计;再次,对于构建复杂的多接口组合的场景用例来说,流量录制的方式还难以做到:对录制下来的流量进行二次加工,可能还不如动一下脑筋去人工实现。
UI 测试用例的录制方式,靠不靠谱?
UI 测试的方式非常直观,也很容易看到业务价值,但是 UI 界面的快速迭代特征导致测试用例的有效生命周期很短,维护成本极高。所以,相比起单元测试和接口测试而言,脆弱、复杂、以及投入产出比低下的 UI 自动化测试,不应该成为我们的主要投入领域,一般用于覆盖关键并且 UI 处于稳定状况的业务。
UI 自动化用例还可以通过录制方式来快速生成,现今比较流行的提供录制方式的自动化测试工具包括:面向桌面程序的 QTP、Ranorex、面向 Web 页面的 Selenium IDE、Chrome DevTools-Recorder、阿里的 UI Recorder、面向移动端程序的星海“鲸鸿”、WeTest UITrace 等。
现在业内存在一个有趣的现象,那就是尽管录制得到的 UI 自动化用例基本上不具备可维护性,但是不少人还是会希望采用录制方式实现自动化。究其根本,是大家对 UI 测试的期望值跟一般自动化测试不一样:承认 UI 界面的易变性导致自动化用例维护成本很高的事实,从而干脆当作“短暂”的、“用完即弃“的回归测试用例——只要录制足够简单快速,大不了过一段时间(下一版本)就废弃重新录制。
出于“物尽其用”的观点,做这样的选择看上去无可厚非,但是也要关注团队成员对这种方式的接受度。毕竟反反复复去做一些注定了要放弃、从头再来的事情,对人的士气打击也是显而易见的。从长远来看,我们还是更希望从相对稳定的层级,比如组件级别去覆盖 UI 的测试,也就是说前移到单元测试环节。毕竟现在前端框架已经很成熟,完全可以基于框架对 UI 进行细粒度的单元测试。而对于端到端的 UI 测试,还是应该谨慎投入。
“聪明”的执行自动化用例
以下自动化测试代指的是集成级别测试,不包括单元测试
写好用例代码是自动化测试重要的第一步,但是只有执行了才能让其价值得到展示。如同自动化收益公式所表述的那样,自动化测试用例执行次数越多越好。而在实践当中,当我们不能每次执行全量回归用例集时,就需要考虑测试范围和效率之间的平衡,有策略地执行自动化用例。下面两点是关于自动化价值的核心原则,需要在自动化测试体系建设中牢记:
原则 1:自动化测试价值的根源在于业务,应该基于业务驱动自动化测试,始终关注优先级最高的需求所对应的测试。
原则 2:自动化测试价值的放大器是测试执行频率,只有跑的次数多了,才能够赚回成本;而只有单轮次执行的够快,才能保障较高的执行频率。
CI/CD 中跑自动化测试,会遇到什么“坑”?
从瀑布模式时代开始,传统的自动化测试执行方式是 nightly-run:设置任务在每天下班后跑全量回归用例,然后第二天拿到结果后再去处理执行失败的 case。而之所以做不到实时跑自动化测试,就是因为自动化用例累积到一定数量之后,需要好几个小时才能执行完一轮。然而在 DevOps 时代并不会有如此“宽裕”的时间留给回归测试,会期望把自动化测试嵌入到 CI/CD 流水线之中,提供及时的质量反馈,例如下面几种情况:
1. 自动化测试嵌入 CI 中,也就是每次把新特性的代码合并(Merge Request)到主干分支之后,除了需要跑一次全量单元测试外,往往会跑一次冒烟测试用例集;
2. 自动化测试嵌入 CD 中,在每次执行生产环境部署动作之前,确认在预发布环境上跑一次全量回归测试用例集;部署完成后还会在生产环节跑一次冒烟测试用例集;
3. 在团队能够实现迭代内及时编写好自动化用例的情况下,CI 过程中还需要执行已完成特性所对应的用例,确保代码变更之间的相互兼容;
4. 甚至在迭代内,团队中的成员可以自由创建 CI 来执行任意一个选定的自动化用例集。
一般来说,CI/CD 对运行时间是非常敏感的,所以在流水线中的集成测试部分不能耗时过长。如果不加控制地扩大测试用例集范围集成进来,那么运行时长必然不受控制。
设想一下,随着代码写的越来越多,每次跑自动化用例所花费的时间越来越长,最后只能降低执行自动化测试的频率……这就比较无语了,明明已经写好了这么多代码,却发挥不了价值,那我们还需要努力写更多自动化用例吗?这样的“坑”相信很多人都碰到过。自动化测试原本功能很强大,但现在被束缚在笼子里面,这样的困境该如何破解呢?既然每次按部就班执行大量测试用例的方式不可取,那就需要找到更优的方法,来实现“更聪明”地执行自动化用例。
要执行得更快,第一个想到的解决方案自然是并行执行,把用例分发到分布式的机器环境中。而要做到这点,则需要设定规则保障用例之间的独立,并且做好测试数据的管理,让每次用例执行改动后的数据都能复原。只是这样做会让复杂度大大增加,而且问题出现后也难以排查。除此之外还有一个很重要的问题:每次跑的都是全量,中间夹杂着大量无效执行的用例,那么因此得到的笼统的通过率又能够说明什么问题呢?除非设定简单的标准,就是要全部通过,但是现实中因为网络抖动导致的延时、复杂的数据更新等等原因,都会导致零零星星的失败。所以,并行执行还不能完全解决问题,我们还需要另辟蹊径:如果跑自动化测试用例时,可以根据变更来确定用例范围,而不是每次执行都覆盖全量用例,是不是就可以控制测试执行的时间呢?答案就是精准测试——通过建立变更和测试的对应关系,从而更有效的验证变更,减少不必要的全局回归。精准测试从实现上大致上包括两种:基于代码变更确定用例集,和基于需求变更确定用例集。
基于代码变更来自动确定用例集,是将测试用例与程序代码之间的逻辑映射关系建立起来,反映的是代码的测试覆盖率。这个目标非常宏伟,只是目前业内的方案还不成熟,还需要搭配大量的人工检查核对。而基于需求变更来确定用例集,则是建立测试用例与业务需求之间的映射关系,反映的是需求的测试覆盖率。这个方法在技术上听起来不是那么高大上,只是通过管理上的约束来实现,但是已经能很好达到我们的目的。CODING 就是基于这样的逻辑打造了自动化用例库,目标是让 1)和 2)的实践得到顺畅落地,乃至实现 3)和 4)的自由执行某个自动化用例集。
CODING 助力实现“业务驱动自动化测试”
CODING 测试产品推崇的是“业务驱动测试”:一切从业务需求出发,通过建立自动化跟业务需求的关联,可以让自动化有的放矢的扩大覆盖率,而不是凭空随意去写测试代码,产生重复的无效测试。
同时,CODING 自动化用例库通过对自动化测试代码进行解析,把得到的函数列表匹配到相应的功能用例,基于已有功能用例与需求的关系,形成自动化用例与需求功能的映射关系:
基于这样的关联关系,我们就可以顺藤摸瓜,根据某个自动化函数运行失败,找到对应哪个需求有问题,然后根据需求的优先级或者影响范围决定是否继续下一步,是打回修复问题还是决定继续发布上线。
首先,建立自动化代码和功能用例的匹配关系,自动化覆盖率“一目了然”。
CODING 提供示例代码导入,帮助用户通过在代码中按照规则在注解/注释中填写功能用例 ID,或者人工判断,让解析出来的自动化代码函数跟已有的功能用例进行匹配,逐步提升功能用例的自动化覆盖率(也为精准测试打下基础):
然后,在迭代进行当中,能够选择性执行特定的自动化用例集。
在普通测试计划中,可以圈选部分功能用例来执行对应的自动化用例;而在迭代测试计划中,圈选特定需求后,对应的功能用例列表也就产生了,从而实现业务驱动自动化用例的执行。CODING 会持续加强灵活执行自动化测试方面的能力,提供更加顺畅的“聪明”执行的场景方案。
总结
自动化测试体系的建设是个系统工程,涉及到人、技术、流程等方方面面,我们尤其需要全局思维、权衡利弊,“没有最好的、只有最合适的方式”。
首先,应该在团队层面对齐自动化测试的目标。因为质量保障是有成本的,不能无限制的投入,没有清晰的质量预期就无从着手去制定实施路径。不论是团队成员的能力建设,还是自动化测试技术框架选型,整体工作流程的设计都必须以此为基准。
其次,技术框架/工具的选型应该要适配现有或者目标工作流程。对于希望打造研发一体化工作模式的中大型团队,在工具选用和流程制定上就需要体现跨职能的协作性,而且要向业务对齐,并且要充分考虑团队的持续效率。比如让自动化测试成为 DevOps 持续集成/持续部署的一环,自动化测试要体现业务价值(保障对业务需求的覆盖),然后关注测试脚手架的可维护性,形成对自动化用例/测试数据的重构,测试执行效率及时调优的制度规范。
最后是对人的发展,业务的变化带来技术和流程变革的要求,最终的落脚点还是要依靠人的成长。我们在制定目标、设计流程、选用工具的时候,会考虑到团队的适配度。团队成员也会随之产生职责变化甚至技能升级要求,需要关注个人体验,并对其进行引导和培养,实现“有温度”的转型。曾经风风火火的“去测试化”风潮的回落,正说明了测试的角色不会消失,测试开发最终还是测试,只是让测试角色“回归”到工程师的本源。
点击开启高效云端研发工作流