做测试这行时间久了你会遇到一个挺常见的现象团队刚开始搞自动化的时候热情都特别高平台选好了环境搭好了测试用例也翻译了一大批。可跑了三两轮问题就来了——用例时不时闪一下红周一早晨第一件事是修昨天晚上的失败通知回归报告没人看用例数越攒越多敢动的代码却越来越少。我见过不止一个团队最后把“自动化测试用例编写”这件事归为“投入大、收益小”。这里面的问题其实很少是工具不好用也很少是代码写不出来。真正影响自动化价值的是写用例的人有没有想清楚一个基本问题你到底在为什么而自动化。这篇文章不聊框架选型也不讲平台搭建只围绕自动化测试用例编写这件事把筛选、设计、稳定性和长期维护讲透。适合刚接触自动化的测试工程师也适合正在被用例稳定性折磨的团队。1. 想清楚哪些用例值得自动化比学会框架重要得多1.1 自动化用例和手工用例是两套设计逻辑很多团队踩的第一个坑是把手工用例直接翻译成自动化脚本。手工用例的目标是覆盖——人坐在电脑前按步骤操作出了问题可以临场判断。但自动化用例的目标是稳定反馈——无人值守地反复执行每一次执行都要把环境、数据、页面状态这些变量控制住。这两套逻辑的差异在于手工执行时人脑可以承担“智能容错”看到页面加载慢了就多等两秒弹出了无关浮层就手动关掉而自动化脚本没有这个能力所有变量都必须事先被设计、被隔离。所以写自动化用例之前最先要做的不是写代码而是做减法——从手工用例里挑出那些确实值得自动化的场景。我把这个思路叫作“先筛后写”。筛得好后续的维护成本能降低一半以上筛得不好写得越多负担越重。1.2 用ROI的思路做取舍自动化不是免费的自动化用例是有成本的。它不只是第一版的编写成本还有之后每一次需求变更带来的维护成本、每一次失败后排查的成本、跑在CI上的机器和时间成本。我见过一条用例因为页面文案频繁调整两个月内被改了十几遍团队最后彻底放弃维护让它一直标红躺在报告里。所以判断一条用例要不要自动化可以朴素地算一笔账。假设一条手工用例每次执行耗时3分钟自动化之后每次执行只需30秒看起来每次省了2.5分钟。但如果这条用例只在发版前跑一次一个月跑两趟一年下来省下的时间相当有限。反过来如果是每天回归的核心链路省下的时间就很可观。适合自动化的用例通常满足这么几个条件执行频次高、场景相对稳定、数据可构造、结果有明确断言。不适合自动化的包括一次性活动页验证、视觉样式核对、依赖人工主观判断的用例、以及需求正在高频变动的功能。我整理过一个对照表团队写用例前会先对一遍适合自动化的用例不适合自动化的用例核心链路冒烟用例一次性营销活动验证每日回归的高频场景视觉样式、交互手感评估需要多环境重复执行的用例需求仍在频繁调整的功能数据可以独立构造的接口用例依赖真实第三方强校验的场景结果有客观断言状态码、落库、返回值需要人眼判断“看起来是否正常”的用例1.3 用例筛选的四条硬标准除了ROI我习惯再用四条硬标准过一遍。第一条是场景是否稳定。需求还在频繁变自动化用例就是写一条废一条。第二条是数据是否可控。用例要创建订单但订单号规则一直在改环境里的测试账号还被别人共用这种用例天生就不适合自动化。第三条是断言是否明确。如果一个场景只能验证“页面没报错”却说不清楚到底什么是对的那写出来的用例价值很低。第四条是失败后是否容易定位。用例失败如果连是哪个环节、什么数据导致都看不出来那它只会变成团队的时间黑洞。1.4 给用例定优先级宁缺毋滥筛完之后我给用例定P0、P1、P2。P0是核心业务链路只要有一次不通过就必须停发P1是高频率回归场景可以允许短暂失败但要当天修复P2是边缘场景允许晚两天处理。这里特别想说一点用例数量真的不是KPI。30条稳定、看结果有用的用例远胜100条跑起来忽绿忽红的用例。自动化用例库的早期阶段最重要的目标是建立信任让团队看到“这条用例确实能拦住问题”而不是攒一堆无人敢信的报告。2. 用例设计四层结构前置、动作、断言、清理一步都不能省2.1 为什么很多用例写出来像“流水账”我Review过很多新手写的用例普遍的问题是把用例写成流水账打开页面、点登录、输入用户名、输入密码、点提交、断言登录成功。表面上功能是通的但一旦用例跑挂了或者需要复用问题就暴露出来了——前置数据从哪来的跑挂了之后脏数据怎么处理别人能不能看懂这条用例在验证什么自动化用例本质上是一段需要长期运行的程序不是一次性手工操作记录。“步骤能跑通”只是及格线真正的要求是独立、可读、可重复、可排查。这也正是为什么我坚持用四层结构来设计用例。2.2 四层结构Setup、Action、Assertion、Cleanup四层结构的核心思想是把用例拆成四个职责边界清晰的部分第一层是前置条件。创建一个独立用户、准备一条测试订单、保证某个环境变量就绪。前置条件必须独立完成绝不能依赖上一条用例运行后的残留状态。第二层是动作。只做被测行为的驱动不夹带无关操作不顺手帮下一个用例准备数据。第三层是断言。验证被测行为实际产生的结果包含状态变化、数据变化、业务规则。第四层是清理。删除创建的数据、恢复环境配置、退出登录态保证下一条用例跑在一个干净起点上。很多人会忽略清理觉得“临时环境无所谓”。但在一个持续集成体系里一条用例不清理很可能会让下一条用例失败下一条又留下了更多脏数据最后变成链式失败。我见过有团队一晚上红一片查到最后竟是一条用例没把测试订单删干净。这里用接口测试场景举个例子。比如要验证“创建订单后金额正确”伪代码可以是这样def test_create_order_and_check_amount(): # setup创建一个独立测试用户 user create_test_user() try: # action用该用户创建一笔100元订单 resp create_order(user.token, amount100) order resp.json()[data] # assertion校验接口响应和订单状态 assert resp.status_code 200 assert order[amount] 100 assert get_order_status(user.token, order[id]) pending finally: # cleanup删除测试订单保证环境干净 delete_order(user.token, order[id])在真实的测试框架里清理逻辑通常会放进fixture的teardown或者钩子里而不是写在用例尾部——因为如果断言中途抛出异常后面的清理代码就执行不到了。用try/finally或者框架自带的teardown机制才能确保清理步骤一定会跑。2.3 断言要验证业务结果而不是验证实现细节断言是自动化用例里最容易被写错的部分。我看到的典型反模式是去断言按钮的颜色、弹窗的文案措辞、某个CSS类名甚至断言接口响应耗时小于某个固定的毫秒数。这些断言写的时候很爽但前端换个主题、后端加个日志字段用例就莫名其妙地红了而产品真正的核心逻辑根本没有被验证。好的断言应该站在用户和业务的视角验证“这件事做成了没有”。比如提交一个表单真正的断言不是“提交按钮变灰了”而是“列表里出现了这条新记录”支付一笔订单真正的断言不是“页面显示支付成功”而是“数据库里的订单状态变成了已支付”。至于响应时间这类性能指标也不建议放在功能回归用例里断言。环境的波动会让你不断地重设阈值最终要么用例天天标红要么阈值放宽到形同虚设。性能问题可以用专门的性能测试来做没必要混在功能用例里。2.4 命名是容易被忽略的设计环节用例命名直接影响排查效率。我见过一个项目用例名全是test_01、test_a、test_b打开报告一看根本不知道哪条失败对应哪个功能还得逐个去翻代码。推荐的命名范式是业务模块 场景 预期结果。比如test_create_order_amount_correct、test_login_with_wrong_password_failed。这样的好处是报告里看到失败条目不看代码就能大致判断影响范围而且命名一旦规范用例重复的问题也更容易暴露——两条用例如果名字撞了说明大概率在测同一个场景。目录结构也建议按业务能力划分而不是按页面划分。按页面划分的问题是当页面重构或者模块重组时整个目录都要跟着搬动按业务能力划分至少在业务逻辑稳定的前提下用例集不用大动干戈。2.5 用例设计不要追求“一条多用”新手还有一个倾向想把一个场景的多种情况写进同一条用例里比如登录用例顺带把错误密码、空用户名、锁定账号全测了。结果就是一条用例特别长失败时只能看到“登录用例失败”具体是哪个分支出了问题还得看日志。更合理的做法是一个场景对应一条用例每条用例只验证一个核心行为。分支多就写成多条独立用例前置数据分开准备。这样虽然用例数量会变多但每一条的逻辑都简单清晰失败时定位成本低维护时也不用担心改一个分支影响其他场景。3. 让用例稳定的三块基石等待策略、元素定位、数据隔离3.1 等待策略固定sleep基本是反模式UI自动化里最经典的稳定性杀手就是固定睡眠。time.sleep(3)这个写法在本地环境或许能跑通但换到CI环境机器一忙3秒还没加载出来用例就挂了反过来如果页面2秒就加载完成每次又白白多等1秒。正确的方式是显式等待轮询某个条件直到条件满足或超时。可以等元素可见、等元素可点击、等某个接口请求完成、等某个文本出现。比如这样from playwright.sync_api import expect def test_user_can_submit_order(page): page.goto(/checkout) page.fill(#address, test address) page.click(#submit-order) # 等待订单创建成功的反馈而不是固定睡几秒 expect(page.locator(.order-success)).to_be_visible(timeout10000)这里的关键点是等待的是业务条件而不是一个固定的时间点。页面有反馈的等反馈有请求的等请求实在没有明确反馈再去轮询一个最终会出现的UI状态。宁可把超时设长一点也不要频繁改成sleep碰运气。接口自动化同样会有等待问题比如下单后异步回调几分钟后才更新状态。这时我一般会写一个轮询函数循环查询订单状态直到满足预期并设置最大等待时间和间隔。盲目sleep(60)不仅慢而且环境波动时照样可能会失败。3.2 元素定位越“笨”的选择越稳定UI自动化的第二个大坑是元素定位。很多人写xpath时图方便直接复制浏览器生成的绝对路径比如//div[3]/div[2]/span[1]。这种定位方式前端只要加一层容器或者挪动两个节点用例就立刻变红。还有用下标定位的ul li[2]这种排序一变就出事。更稳定的做法是给页面元素加上专门供自动化使用的属性比如>import time def unique_username(): return ftest_user_{int(time.time() * 1000)}清理动作同样要设计成幂等的——删数据时如果数据不存在也不要报错删不干净的场景至少保证下次运行不会被它影响。3.4 随机性问题验证码、时间、第三方回调都要提前拆掉凡是影响用例结果的随机因素必须在用例设计阶段处理掉。最典型的是登录验证码真实环境下一个验证码只能用一个自动化工序没法稳定操作。我的建议是测试环境必须有验证码旁路开关或者使用固定的测试验证码。没有这个条件这条用例就不该自动化而不是反复写重试逻辑。时间因素也要注意。比如一条用例依赖“当前是上午9点才显示某个入口”那这条用例就需要能手动控制时间否则只能在特定时间段执行。第三方支付回调、短信通知这类依赖建议在测试环境用模拟服务替代而不是真的去调外部接口——外部服务一抖动用例就跟着闪红。3.5 失败重试止痛药不是治疗方案很多团队为了把用例跑绿会加失败重试机制第一次失败了自动再来一次。这不是不能用但一定要明白重试会掩盖真实缺陷。如果一条用例第一次失败、重试成功CI是绿的但那个失败可能正是产品的一个bug只是碰巧被重试躲过去了。我的建议是允许对明显是环境抖动导致的失败做有限次重试一般一次就够但重试时一定要把第一次失败时的日志和截图保存下来。一旦某条用例反复触发重试就说明它本身不稳定要当成用例缺陷去修而不是继续调大重试次数。重试次数调得越大团队对自动化报告的信任度就越低。4. 用例不只是一段脚本分层运行、同行评审和持续治理4.1 用例分层不要把鸡蛋放在一个篮子里用例写好了下一步是设计运行策略。很多团队把所有用例都放在一个大集合里提交一次代码就全跑一遍跑一次一两个钟头谁都不愿意看结果。更合理的做法是把用例分成三层。冒烟层只放P0的核心链路十几条到几十条每次代码合并都跑要求10分钟以内出结果。回归层放P1的核心场景每天定时跑一次半小时到一小时出结果。全量层放P2的边缘场景每周或者每次发版前跑一次。分层的目的是让反馈速度和执行成本匹配。合并时代码还没合快速反馈最重要发版前 полноценная全量回归时间成本可以接受。如果不分层用全量集去给每次提交把关结果就是等待时间过长、用例频繁失败大家逐渐无视报告。4.2 写用例也要过Review用例的代码评审和产品代码评审一样重要甚至比产品代码更需要控制质量——因为用例坏了不会线上事故但会让整个团队对自动化失去信心。我建议每条新用例合入之前至少过一遍这样的检查清单这条用例是否依赖其他用例产生的数据或状态断言是否在验证真实的业务结果而不是实现细节是否已经存在覆盖相同场景的用例用例失败时看日志能不能在5分钟内定位到原因清理步骤是否一定会执行这五个问题问下来大部分拍脑袋写的用例都会被拦下来。定期做Review还有一个额外的好处在这个过程中大家会对业务规则和自动化边界达成共识而不是各自按各自的理解写用例。4.3 失败用例的治理先分类再处理用例跑了总会失败关键是怎么对待失败。我见过一种很差的做法是用例红了看一眼不是自己模块的就不管了或者只要报告变绿就当无事发生。这样过两周报告就没人看了。失败用例要分类处理第一类是产品缺陷说明测试真的发现了问题这种情况要尽快提单第二类是环境问题比如依赖服务没起、参数配置被改这类要修环境而不是改用例第三类是用例自身的问题比如定位器失效、等待时间太短、数据没有隔离这类要当日修掉。我的习惯是统计每条用例的周失败率。失败率超过10%的用例要专项治理要么修好要么暂时禁用绝不允许一直挂红。一条天天失败的用例带给团队的不是“自动化无用”的结论而是让所有其他绿用例的可信度也被一起拖下水。4.4 用例去重数量不代表质量用例库跑了一段时间后一定会出现重复覆盖。这个新同事加了一条A场景用例那个老同事早就有了一条类似的只不过数据不同、断言稍微不同。重复用例占用了执行时间还增加了维护量——每个涉及的业务规则一变要同步改好几条。控制重复的办法是维护一张功能覆盖矩阵。横轴是核心功能点纵轴是用例矩阵上标注每条用例覆盖了哪些功能点。每个功能点有一条用例覆盖就够了重复的果断删掉。删用例对许多人来说是件心理上很别扭的事总觉得“删了会不会覆盖不了了”但只要矩阵上标注清楚风险完全可控。4.5 维护责任谁写谁养自动化用例维护必须有明确责任人。我的原则是谁写的用例谁负责养至少养到它稳定下线。新用例合入时编写者要对它的未来一段时间的稳定性负责如果用例在持续集成里反复失败责任人要及时响应。我还建议把用例维护排进迭代任务里而不是当作“有空再说”的额外负担。每周固定抽一点时间处理上周的失败用例、清理无价值用例、更新受需求变更影响的脚本。不做这件事用例库就会像一座没人扫的大楼灰尘越来越多最终只能推倒重来。把自动化测试用例编写浓缩成一句话我更愿意用“稳定、可读、可删”这六个字。稳定是让团队敢置信这个系统可读是让别人愿意接手和维护可删是承认用例库里必然有一部分会失去价值及时清理比硬撑着保留更重要。我见过太多自动化项目死掉不是因为没人会写代码而是因为用例库被越堆越重最后谁都不敢碰。少写一点写稳一点跑起来能看到问题——这大概就是自动化最理想的状态了。