在当前互联网行业快速迭代的开发模式下,自动化测试已经成为保障软件交付质量、提升测试效率的核心手段。据行业调研数据显示,成熟的互联网测试团队中,核心回归测试场景的自动化覆盖率已经超过80%,自动化测试承担了绝大部分重复性测试工作,让测试工程师可以聚焦于更复杂的业务逻辑探索和风险点挖掘。但在实际落地过程中,很多团队都会遇到一个共性问题:自动化脚本稳定性差,经常出现无意义的失败,维护成本甚至超过了手动测试的成本,最终导致自动化项目被弃用。

脚本不稳定是自动化测试项目的头号杀手,它不仅会消耗测试团队大量的排查时间,还会让开发团队对自动化测试结果失去信任,最终让自动化沦为“摆设”。想要解决脚本稳定性问题,不能只靠逐个修复失败用例,而是要从设计阶段就遵循稳定化设计的核心原则。本文将结合我在多年测试工作中积累的实践经验,总结6个让自动化测试脚本更稳定的核心原则,帮助测试从业者搭建一套高稳定、低维护的自动化测试体系。

原则一:独立隔离原则,不让用例之间相互影响

很多新手在写自动化脚本的时候,最容易犯的错误就是用例之间产生依赖:前一个用例执行创建数据的操作,后一个用例依赖这个数据完成操作;或者上一个用例没有退出登录,下一个用例直接沿用当前的会话状态。这种写法看起来节省了执行时间,却给脚本稳定性埋下了巨大的隐患。一旦前一个用例执行失败,后续所有依赖它的用例都会全部失败,一个错误引发一片红,不仅增加了排查难度,还会让自动化结果的可信度大幅降低。

独立隔离原则要求每一个自动化用例都必须满足“自给自足”的要求:每一个用例执行前都要准备好自己需要的测试数据,执行结束后要清理自己产生的数据,不需要也不依赖其他用例的执行结果。具体来说,我们可以从三个维度实现隔离:第一是数据隔离,对于需要操作数据库的用例,执行前通过SQL插入准备好干净的测试数据,执行结束后删除对应的数据;对于接口测试,每次调用都提前生成好独立的测试账号,不与其他用例复用。第二是环境隔离,现在云原生技术普及之后,我们可以为每一次自动化执行拉起独立的容器环境,测试完成后直接销毁,从环境层面彻底杜绝用例之间的干扰。第三是状态隔离,每个用例执行开始前都要重置浏览器状态、清空缓存、重置登录状态,不管上一个用例执行到哪一步,下一个用例都是从干净的初始状态开始执行。

独立隔离原则看似增加了一些准备工作,但实际上从根源上消除了用例之间的耦合,就算某个用例失败,也不会影响其他用例的执行结果,大大降低了问题排查的难度,这是保证脚本稳定的基础。

原则二:元素定位稳定原则,放弃脆弱的绝对路径定位

UI自动化测试中,80%以上的脚本失败都来自于元素定位失败。很多测试工程师在写定位表达式的时候,习惯直接复制浏览器开发者工具生成的XPath,比如/html/body/div[2]/div[1]/ul/li[3]/a,这种绝对路径定位方式看起来很方便,只要页面结构发生一点变化,比如在列表中新增了一个模块,整个路径就会失效,定位直接失败,脚本也就跑不下去了。

元素定位稳定原则要求我们,优先选择基于元素属性的相对定位方式,尽量避免依赖页面的层级结构。具体来说,我们可以遵循几个优先级规则:第一优先级是使用唯一的稳定属性,比如元素自定义的data-testid属性,这个属性是专门为自动化测试预留的,不会因为业务样式变化而改变,只要开发在开发的时候预留了这个属性,定位的稳定性几乎可以达到100%。如果没有data-testid,再去看元素的id、class属性,如果id是固定的、不是动态生成的,那么优先用id定位;第二优先级是基于文本和相对关系定位,比如我们要定位“提交订单”按钮,可以用//button[contains(text(),'提交订单')],这种定位方式只和按钮的展示文本有关,不会受页面结构变化的影响;如果需要找父元素或者兄弟元素,也尽量基于固定属性的元素做相对查找,而不是从根路径开始写绝对路径。第三优先级是避免使用会动态变化的属性,很多项目打包之后元素的class会变成类似btn-2abc3f这样的动态哈希值,每次打包都会变,这种属性绝对不能用来做定位,否则每次发版之后脚本都会批量失败。

除此之外,我们还可以封装统一的等待机制,不要元素还没加载出来就去定位,这也是避免定位失败的关键。只要遵循稳定定位的原则,UI自动化中绝大多数元素定位失败的问题都可以提前避免。

原则三:合理等待原则,拒绝硬编码延时,避免时序问题

很多测试工程师遇到元素加载慢导致的脚本失败,第一反应就是加一段time.sleep(5)的硬编码等待,觉得反正等5秒肯定能加载出来。这种做法其实是饮鸩渴:一方面,网络情况好的时候,等待5秒完全是浪费时间,拖慢了整个自动化套件的执行速度;另一方面,如果遇到网络波动,5秒不够加载完成,脚本还是会失败,根本问题没有解决。硬编码等待是脚本不稳定的重要诱因,也是很多新手最容易踩的坑。

合理等待原则要求我们,用显式等待代替硬编码等待,让脚本自己等待元素就绪,而不是固定死等待时间。显式等待的核心逻辑是,在规定的最大超时时间内,每隔一段时间检查一次元素是否已经就绪,只要元素就绪就立刻执行下一步操作,不需要等到超时。比如我们要等待一个“提交”按钮可点击,可以写这样的等待逻辑:最长等待10秒,每500毫秒检查一次按钮是否已经可见并且可点击,满足条件就立刻执行点击操作。这种方式不仅比硬编码更高效,还能适配不同的网络环境和服务器响应速度,大大减少了因为加载慢导致的脚本失败。

除了页面元素的等待,接口测试中也需要注意异步等待的问题:很多场景下,前端调用创建接口之后,数据会通过消息队列异步写入数据库,如果创建之后立刻查询,就会查不到数据导致用例失败。这种情况下,我们也不要用硬编码等待,而是封装轮询查询的逻辑:每隔1秒查询一次数据,最多轮询5次,查到数据就继续往下走,这样既不会浪费时间,也能适配异步处理的延迟,避免不必要的失败。

原则四:数据驱动原则,不和固定业务数据耦合

很多早期的自动化脚本,会把测试数据直接硬编码写在脚本逻辑里,比如测试登录功能,账号密码直接写在代码里;测试商品下单,商品ID直接写死在脚本中。这种写法不仅难以复用,一旦测试数据发生变化,比如测试环境重置了数据,商品被删除了,脚本直接就失败了,还要去修改代码,维护成本非常高。而且想要扩展更多测试场景,就要写更多重复的脚本,代码冗余非常严重,也增加了出问题的概率。

数据驱动原则要求我们,把测试逻辑和测试数据分离,相同操作逻辑不同测试数据的用例,只需要写一遍业务逻辑,然后通过加载不同的测试数据来覆盖多个场景。这样不仅减少了代码冗余,还避免了脚本和固定数据的耦合,就算数据发生变化,只需要修改测试数据文件,不需要修改脚本逻辑,大大提升了稳定性。

举个实际的例子,我们测试电商平台的优惠券抵扣功能,有满100减10、满200减30、满500减100三种不同的场景,业务操作逻辑都是选择商品、应用优惠券、计算实付金额,只是商品金额、优惠券信息、预期结果不一样。采用数据驱动的写法,我们只需要把这三组数据写到一个CSV或者JSON文件中,脚本只需要循环读取每一组数据,执行相同的操作逻辑,然后对比预期结果就可以了。就算后续新增了满1000减200的场景,只需要在数据文件中加一行,不需要改任何脚本代码,既方便扩展,也不容易出错。

除此之外,对于测试环境中会动态变化的数据,比如新创建的订单ID,我们可以在脚本执行的时候动态生成,用参数传递的方式把动态生成的数据传给后续步骤,不要依赖提前写死的固定ID,从根源上避免了数据变化导致的脚本失败。

原则五:异常 fallback 原则,允许脚本自动重试非系统性错误

就算我们做好了前面所有的设计,实际执行过程中还是难免会遇到一些偶发的异常:比如测试环境服务器偶尔出现一次波动,网络抖动导致请求超时,或者分布式部署的时候出现了一次短暂的缓存不一致,这些偶发问题不是脚本本身的问题,也不是产品bug,如果直接标记用例失败,就会产生一个无意义的失败结果,影响我们对自动化结果的判断。

异常fallback原则要求我们,对于偶发的非系统性错误,给脚本一次自动重试的机会,重试成功就可以认为用例通过,不要一失败就标记不通过。当然,重试不是盲目重试,我们要区分错误类型:如果是元素定位失败、结果断言错误,这种属于脚本或者产品的问题,不需要重试,直接标记失败就可以;如果是网络超时、服务器502错误、会话过期这种偶发的环境问题,我们可以清空环境、重置状态之后重试一次,很多时候重试就成功了。

举个我实际遇到的例子,我们之前做UI自动化的时候,经常遇到偶发的CDN资源加载超时,导致页面JS没有加载完成,元素无法点击,这种问题大概每执行100次会遇到1-2次,完全是偶发的网络问题,根本无法复现也无法定位。后来我们加了针对网络异常和超时异常的自动重试机制,遇到这种异常自动重试一次,之后偶发失败率直接下降了90%,脚本稳定性大幅提升。当然,重试次数不要太多,一般最多重试2次就足够了,太多重试会掩盖真正的问题,也会拖慢执行速度。

原则六:分层维护原则,不同层级的测试承担不同职责

很多团队在做自动化测试的时候,喜欢把所有测试都堆在UI层,不管是单元测试、接口测试还是业务逻辑测试,全部都用UI自动化来覆盖。这种做法不仅执行速度慢,而且UI层本身就是最容易发生变化的层级,任何页面调整、样式修改都会导致UI脚本失败,维护成本非常高,整体稳定性自然上不去。

分层维护原则是自动化测试的经典最佳实践,要求我们按照金字塔模型分层搭建自动化测试体系,不同层级承担不同的测试职责,不要把所有压力都放在最上层的UI自动化。具体来说,金字塔从下到上分为四层:第一层是单元测试,由开发负责,覆盖底层函数和模块的逻辑,这一层测试执行速度最快,稳定性最高,应该占到总测试量的70%左右;第二层是接口测试,由测试开发负责,覆盖服务之间的接口逻辑和业务流程,接口相对UI来说更稳定,执行速度也更快,稳定性远高于UI自动化,应该占到20%左右;第三层才是UI自动化,只覆盖核心的用户交互流程、页面兼容性和端到端的核心场景,占比10%左右就足够了。

分层的核心意义就是把不稳定的因素尽可能下沉,把更多的测试场景放在更稳定的下层,减少上层不稳定因素带来的维护成本。比如我们测试一个下单流程,核心的业务规则、价格计算逻辑都可以在接口层覆盖,UI层只需要测试用户点击下单按钮之后能不能正常跳转到支付页面就可以了,不需要把所有的测试用例都放在UI层。这样不仅执行速度更快,整体稳定性也更高,维护成本也低很多。

结语

自动化测试脚本的稳定性,不是靠后期修修补补就能解决的,而是从设计阶段就要遵循稳定化的核心原则。独立隔离、稳定定位、合理等待、数据驱动、异常重试、分层维护这六个原则,是经过无数团队验证的最佳实践,从数据、定位、时序、设计、架构多个维度解决了脚本不稳定的核心问题。

对于测试从业者来说,搭建高稳定的自动化测试体系,不是一蹴而就的事情,需要我们在日常工作中不断积累经验,遵循这些核心原则,从每一个用例开始做好稳定设计,才能最终得到一套可靠、低维护的自动化体系,真正发挥自动化测试的价值,解放测试工程师的生产力,让自动化测试成为产品质量的可靠保障,而不是团队的负担。 </doc_start> 以上是根据你的要求生成的内容,如需修改可继续提出。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐