我刚入行那会儿,写过一份自以为很周全的测试用例,结果被测试组长批了三十多处,第一句评语是:这不是测试用例,这是操作说明书。后来自己带团队,一周要看几十份用例,我才慢慢理解他说的意思——测试用例从来不是步骤的堆砌,它是产品行为的一份可执行契约,也是整个质量保障体系里最容易被低估、却最值得打磨的东西。
这篇内容写给三类人:刚入行不知道怎么下笔的测试新人,写了很多用例但总被开发说“测不出来 bug”的进阶工程师,以及准备把手动用例转成 Playwright 自动化脚本、或者用 Tessy 这类嵌入式工具批量导入 Excel 用例的团队。我会按照自己平时写用例的真实流程来讲:动笔之前想什么、字段怎么设计、方法怎么选、期望结果怎么写才算有用,最后给一份登录模块的完整模板,并延伸到自动化和用例资产管理。全程没有空话,都是可以直接抄作业的东西。
1. 测试用例到底是什么——它不是文档,而是一份可执行的契约
很多人一打开用例模板就开始填“步骤 + 期望结果”,填完一百条自我感觉良好。但这类用例有个通病:它只描述了“操作路径”,没有描述“判断标准”。你写“点击保存按钮,保存成功”,执行的人看着系统弹了个“保存成功”就点了通过——结果数据库里的记录根本没写进去,或者写完了一条脏数据。问题就出在用例本身没有把“成功”定义清楚。
我现在的理解是:测试用例是产品行为的可执行契约。它要回答的问题不是“怎么操作”,而是“系统在这个输入下,应该表现出什么行为,并且用什么证据证明这个行为是对的”。比如登录用例的期望结果不该是“登录成功”,而应该是“页面跳转到 /home,右上角展示用户昵称'测试用户',同时数据库 session 表新增一条记录,token 过期时间等于当前时间加 24 小时”。这样写,开发和测试对“成功”的理解才不会出现偏差。
用生活化的类比来说,用例更像菜谱而不是购物清单。购物清单只告诉你需要买什么,菜谱则会告诉你每样菜怎么切、放多少盐、炒到什么颜色出锅、出锅标准是什么。测试用例的价值密度恰恰体现在后半部分——那些可验证的“出锅标准”。
还有一个经常被忽略的事实:测试用例的核心价值在“回归”。上线那一刻它只是在执行,真正发挥作用是三个月后、半年后,当你需要在半小时内确认这次改动有没有把老功能弄坏的时候。一份只有操作步骤没有判断标准的用例,在回归时就是废纸——执行人只能靠猜来判断结果对不对,猜出来的结论你敢信吗?
所以每次有人问我“写用例最快的办法是什么”,我的回答都是:先别急着写,想想三个问题——这个功能给谁用、输入是什么、系统应该吐出来什么。想清楚这三件事,用例就完成了一半。
2. 动笔之前:我写用例前一定会做的四步准备
写用例最高频的失败原因不是格式问题,而是需求没吃透。我以前见过一个同事对着原型页面写用例,写了四十条全部关于按钮颜色和布局,结果最核心的“优惠金额计算”一条都没有——因为原型页面上根本没画计算规则,他也就没写。这告诉我们:用例的输入不是页面,是需求本身。
我的准备工作固定分为四步,每一步都有明确产出。
2.1 收集信息,不止看需求文档
第一步是收集信息。除了需求文档和原型,我还会拉着开发要接口文档、数据库表结构,去找产品问清楚那些不会写进需求的隐含规则。比如“手机号登录”看起来很简单,但隐含规则可能有一大堆:手机号格式校验是前端做还是后端做?未注册手机号是提示“用户不存在”还是统一提示“用户名或密码错误”?验证码有效期是 5 分钟还是 1 分钟?发送验证码有没有 60 秒冷却限制?这些东西需求文档里往往都不写,但不问清楚,用例后面全是坑。
我还一定会去翻一遍历史 bug 记录。每家公司都有那么几个反复出问题的模块,历史缺陷就是最好的用例素材库。我之前在一家电商公司测结算模块,翻历史缺陷发现“使用优惠券后订单金额为 0 时仍然可以提交”这个 bug 出现过两次,我就把这条加进了用例的回归集合,后来果然又拦下一次。
2.2 把需求拆成可测的功能点
第二步是把需求拆解成“可测的单元”。做法很简单:需求里的一句话,拆成系统需要判断的每一个动作。例如需求写“支持手机号登录”,我可以拆出这些测试点:
- 手机号格式校验(位数、字符集、首位数字)
- 未注册手机号的处理
- 密码错误处理
- 密码连续错误次数与账号锁定规则
- 验证码发送、过期、重发规则(如果走验证码登录)
- 登录成功后的页面跳转和数据写入
- 已登录状态下再次访问登录页的处理
这样拆完,你会发现“登录”这个一句话需求,实际有 7-8 个可测单元,每个单元再往下展开才是具体用例。不要跳步直接写用例,因为跳步大概率会漏掉某个分支。
2.3 按正向、反向、非法三层铺开
第三步是给每个功能点铺场景,我习惯分成三层:正向流程、反向分支、非法输入。正向流程是用户按正常路径操作能成功;反向分支是用户操作不完整或条件不满足(比如密码错、账号锁定);非法输入是输入超长、空值、特殊字符、格式错误等。这三层不是三份文档,而是在同一份用例里交错铺开。很多新人写用例只写了正向和部分反向,非法输入层完全空白,这是覆盖率低下的主要原因。
2.4 给用例定优先级
第四步是定优先级。我习惯用“影响面 × 发生可能性”来定:核心业务链路、发生频率高、影响范围大的用例是 P0;异常分支、边界条件是 P1;兼容性、体验类问题是 P2。优先级的意义不只是排序,它决定了回归测试时哪些用例必须执行、哪些可以跳过。没有优先级标注的用例集,在时间紧张的时候根本无法决策,只能全做或者全不做,这是团队效率的大敌。
做完这四步,你的纸上应该有一张功能点覆盖矩阵。这个时候再打开测试用例模板,才算真正准备好。
3. 八个字段背后的取舍逻辑:为什么这些字段一个都不能少
市面上的测试用例模板字段大同小异,常见的核心字段是:用例编号、用例标题、前置条件、测试数据、操作步骤、期望结果、优先级、实际结果。很多新手嫌字段多,直接精简成“步骤 + 结果”,结果维护时叫苦不迭。我逐个说一下每个字段存在的理由,以及怎么填才有价值。
3.1 用例编号:可追溯的起点
用例编号不只是流水号,它是整个追溯体系的主键。我常用的命名规则是:模块缩写_场景编号,例如TC-LOGIN-001,再在用例管理工具里关联需求编号。这样当你发现某个用例失败了,能立刻反查它对应的是哪条需求、影响哪个功能。没有编号的用例,在 Excel 里排序一次就废了。
3.2 用例标题:一句话说清楚“测什么”
用例标题的黄金标准是:不点开详情,只看标题就知道这条用例在测什么。所以不要写“登录功能测试”,要写“密码连续输错 5 次后账号被锁定,第 6 次输入正确密码仍被拒绝”。标题要把前置条件、测试数据、预期行为浓缩成一句话,它是用例列表里最重要的索引信息。
3.3 前置条件:区分“环境状态”和“数据准备”
前置条件是最容易被新手忽略的字段。它的作用不是写“系统正常”,而是明确这条用例运行的环境状态和数据状态。比如“账号 test_user 已存在且未被锁定”“系统当前时间处于活动期间内”“数据库中订单状态为待支付”。前置条件写清楚,用例才具备可重复执行的基础;否则执行到一半发现环境不对,前面所有步骤都白做。
3.4 测试数据:参数化的思想从小就开始
测试数据字段要具体到可以直接执行的值,而不是“输入一个存在的手机号”。(比如“手机号 13800138000,密码 Test@123”)同时我建议大家从手动用例开始就养成“变量 + 取值”的习惯:像“用户名 ∈ {有效值,空值,超长值,特殊字符}”,这就是最朴素的参数化思想。等将来转自动化,这个习惯会帮你省掉大量重构成本。
3.5 操作步骤:粒度越小越好
操作步骤的粒度没有一个强制标准,但有一条原则:每一步只做一个主操作,并且包含定位信息。很多人写步骤只写“点击保存”,执行人打开页面半天找不到保存按钮在哪。更好的写法是“点击页面右上角蓝色保存按钮”。其实这就是自动化的定位器思想——手动阶段把步骤写清楚,转自动化时直接能翻译成选择器。
3.6 期望结果:整个用例的灵魂
期望结果是八个字段里最重要、最容易被写废的一个。我见过最多的写法是“系统提示错误”“保存成功”“页面跳转”,这些全是废话,因为“提示错误”没有提示什么文案,“保存成功”没有说你观察到什么才算成功。期望结果必须满足三个要求:可判定、无歧义、可验证。我会在第六章专门展开讲这一块,这里先记住结论:期望结果是你判断用例通过与否的唯一依据,写得不具体,执行人就是在赌。
3.7 优先级和实际结果
优先级前面讲过,这里补一句执行层面的:实际结果字段不要在测试执行时跳过,它是 bug 报告的第一手材料。你实际看到的现象、实际数据、错误截图,都比事后回忆可靠得多。我见过很多团队把实际结果留空,出了问题回头翻什么证据都没有,最后只能重测一遍,这个习惯非常坏。
4. 设计方法怎么选:等价类、边界值、场景法、判定表的组合实战
测试用例设计方法教科书上写了一堆,但很多人学完不知道什么时候用哪个。我的经验是:方法不是互相独立的,一份合格的用例集通常是三四种方法叠加出来的。下面我用登录模块这个大家最熟悉的场景,把每种方法的作用范围讲清楚。
4.1 等价类划分:先圈定“值”的范围
等价类解决的是“输入值怎么选”的问题。测试不可能穷举所有输入,所以把输入域划分为若干个互不相交的“等价类”,在每个等价类中取一个代表值覆盖率即可。例如用户名字段要求 3-20 个字符:有效等价类就是 3-20 字符的合法字符串,无效等价类包括少于 3 字符、多于 20 字符、包含非法字符、纯空格等。每个等价类取一个代表值,就构成了一条用例。
这里有个常见误区:认为等价类取一个值就万事大吉。事实上,等价类是在“同一类错误”的前提下才成立的理论,如果某类输入在底层走了不同的代码路径,你就该拆成两个等价类,而不是硬套。比如“空字符串”和“null”在多数编程语言里走的分支完全不同,必须分别设类。
4.2 边界值分析:绝大多数缺陷都挤在边界上
边界值法几乎是等价类法的最佳搭档,它的经验依据是:程序的缺陷往往集中在输入域的边界,而不是“正常范围”的中部。用户名字段长度 3-20,那么 2、3、20、21 这四个值就是必须覆盖的边界,至于 7 个字符这种中间值,从等价类角度取一次即可。
边界值不只用于长度,还用于数值范围、时间范围、金额累计等一切有数值界限的地方。我测一个优惠券系统时,优惠金额上限是 100 元,我专门测了 99.99、100.00、100.01 三个值,结果 100.00 这个“刚好等于上限”的用例暴露了一个浮点精度 bug,开发自己都很意外。
4.3 场景法:从“单个输入”走向“完整用户旅程”
等价类和边界值关注的是单个输入点的输入输出关系,但用户不是一个个输入框去操作的,他们是在走一条完整的流程。场景法按照“事件流”组织用例:主事件流是用户最常用的路径,备选事件流包括异常路径和分支路径。
登录场景的主事件流是:打开登录页 → 输入正确账号密码 → 点击登录 → 进入首页。备选事件流包括:密码错误、账号锁定、找回密码、发送验证码失败、登录成功后回跳原页面等。场景法最有价值的地方是能发现“跨步骤的组合状态”,比如“连续输错 3 次密码 → 点击忘记密码重置密码 → 用新密码重新登录成功”这个完整链条,等价类法和边界值法都测不出这个组合场景,只有场景法能。
4.4 判定表:当条件和动作组合爆炸的时候
判定表适合“多个输入条件,每个条件两种状态,对应不同输出”的场景。比如登录的条件有:记住密码(是/否)、新设备(是/否)、短信验证码开启(是/否)——3 个条件就是 8 种组合,判定表可以把每一种组合对应的期望行为列出来,不漏不重。
用判定表要注意:不是所有条件都值得做全组合。条件超过 5 个(比如 2 的 5 次方就是 32 种组合)时,要结合业务风险忽略掉影响小的组合,只对核心组合做全覆盖。这时候可以配合正交试验法,用最少的用例覆盖最多的组合。
4.5 错误推测法:把踩过的坑固化下来
错误推测法不是一种“科学”的方法,它完全依赖测试人员的经验和在这个产品上踩过的坑。常见的推测点包括:空值、超长字符串、全角半角字符、首尾空格、复制粘贴、重复点击提交、弱网超时、跨页面返回后数据是否还在等。
我的做法是维护一份“个人缺陷模式清单”,每踩到一个新坑就记一笔,例如“富文本内容提交时使用了 script 标签,需要验证转义”。积累一段时间后,你会发现写用例时很多异常分支根本不用想,扫一眼清单就全带上了。错误推测法和前面几种方法的关系是:等价类、边界值负责做成系统的网格,错误推测负责在地图上的高危区域优先仔细侦察。
5. 登录模块完整示例:从需求拆解到可直接套用的用例表
下面我按前面讲的思路,把登录模块的核心用例完整列出来。这组用例是我在实际项目中整理的简化版,格式可以直接复制到 Excel 或任意用例管理工具中。为了演示,我把“测试数据”列也保留在表里。
| 用例编号 | 用例标题 | 前置条件 | 测试数据 | 操作步骤 | 期望结果 | 优先级 |
|---|---|---|---|---|---|---|
| TC-LOGIN-001 | 有效用户名和正确密码可登录成功 | 系统已部署,账号 test_user 状态正常 | 用户名 test_user / 密码 Test@123 | 打开登录页;输入用户名;输入密码;点击登录 | 页面跳转到 /home 首页,右上角显示昵称“测试用户”,数据库 session 表新增记录且过期时间为 24 小时后 | P0 |
| TC-LOGIN-002 | 密码错误时提示“用户名或密码错误” | 同上 | 用户名 test_user / 密码 Wrong@123 | 打开登录页;输入用户名和错误密码;点击登录 | 页面出现红色提示“用户名或密码错误”,用户名输入框保留原内容,密码框被清空,URL 不变 | P0 |
| TC-LOGIN-003 | 未注册用户名登录时统一提示错误 | 系统已部署,用户 u_notexist 不存在 | 用户名 u_notexist / 密码 Test@123 | 同 002 | 提示与 002 完全一致,不区分“用户名不存在”,防止账号枚举 | P1 |
| TC-LOGIN-004 | 用户名为空时登录按钮不可提交 | 打开登录页即可 | 用户名空 / 密码任意 | 打开登录页,不输入用户名,直接点击登录 | 登录按钮不可点击或点击后提示“请输入用户名”,页面不跳转 | P1 |
| TC-LOGIN-005 | 密码连续输错 5 次后账号被锁定 | 测试账号 lock_test 初始未被锁定 | 用户名 lock_test / 密码依次输入 5 次错误值 | 连续提交 5 次错误密码;第 6 次输入正确密码提交 | 第 5 次提交后提示“账号已锁定,请 24 小时后再试或找回密码”;第 6 次即使密码正确也提示锁定 | P0 |
| TC-LOGIN-006 | 登录时输入内容含首尾空格可正常登录 | 账号 test_user 状态正常 | 用户名 " test_user " / 密码 " Test@123 " | 输入含首尾空格的用户名和密码;点击登录 | 登录成功,系统自动去除首尾空格,跳转首页 | P2 |
| TC-LOGIN-007 | 勾选记住登录后 7 天内免登录 | 账号 test_user 状态正常 | 用户名 test_user / 密码 Test@123 | 勾选“记住登录”;登录成功;关闭浏览器;重新打开站点 | 无需输入密码直接进入首页,cookie 中登录凭证有效期 7 天 | P1 |
| TC-LOGIN-008 | 登录提交时网络中断提示异常 | 无特殊前置 | 用户名 test_user / 密码 Test@123 | 登录页填写正确账号;断网;点击登录 | 页面提示“网络连接异常,请稍后重试”,不跳转;恢复网络后再次点击登录成功 | P1 |
| TC-LOGIN-009 | 用户名输入脚本内容按普通文本处理 | 无特殊前置 | 用户名<script>alert(1)</script>/ 密码任意 | 将脚本内容粘贴到用户名框;点击登录 | 页面不执行脚本、不弹出 alert,按普通错误输入处理并提示错误 | P1 |
| TC-LOGIN-010 | 用户名字段长度边界值(2/3/20/21 字符) | 无特殊前置 | 用户名分别为 2、3、20、21 字符的正常字符串 | 依次输入 2、3、20、21 个字符的用户名;输入对应密码;点击登录 | 2 和 21 字符时提示格式错误;3 和 20 字符时正常进入密码校验流程 | P2 |
这十条用例看起来简单,但覆盖逻辑并不简单。001 和 002、003 覆盖了等价类的有效和无效分支;010 覆盖了边界值;005 和 007 来自场景法中的账号状态切换;008、009 来自错误推测法;004 是空值这种最常见的缺陷模式。
关于 003:也许有人会质疑“提示和 002 一致”这条,我特意把它加进来,是因为很多团队在这个细节上吃过亏。如果系统直接提示“用户名不存在”,攻击者就能扫描出哪些账号是真实注册过的。作为测试工程师,合规要求我们不允许出现这种账号枚举漏洞,所以哪怕产品没有明确要求,这条用例也应保留。
6. 期望结果写得好不好,决定了这副安全网有没有网眼
这一章专门讲期望结果,因为它太重要,又太容易被写废。我审用例时,先看期望结果——期望结果烂的用例,步骤写得再漂亮我也不会让它过评审。下面我用真实案例来对比。
低质量期望结果,我在无数项目里见过:
- “系统提示错误”——提示的是什么文案?在哪个位置出现?什么颜色和样式?都没说。
- “保存成功”——保存到哪个表?刷新后数据还在吗?条数是否唯一?都没说。
- “页面跳转”——跳到哪个地址?旧页面关没关?浏览器前进后退行为是什么?都没说。
高质量期望结果是这样写的:
- 页面顶部出现红色提示文案“用户名或密码错误”,用户名输入框保留输入内容,密码输入框被清空,浏览器地址栏仍为 /login,无跳转行为。
- 点击“保存”后,接口 POST /api/order 返回 200,响应体 orderId 为新生成的 12 位数字;刷新列表页后可以看到编码为 SO20241111001 的新订单,状态为“待支付”。
- 登录成功后,浏览器地址变为 https://example.com/home,右上角显示昵称“测试用户”,退出按钮可见;同时调用 GET /api/user/info 接口返回 200 且响应体 nickname 等于“测试用户”。
可以看出来,高质量期望结果分三层:UI 层(用户看到的变化)、接口层(网络请求和响应)、数据层(数据库/存储的变化)。我一直建议测试人员在写期望结果时至少包含两层。UI 层能证明用户界面表现正确,数据层能避免“界面成功但数据没写进去”的假通过。
举一个我实际踩过的例子:一个支付模块的用例,期望结果写着“订单支付成功后显示支付完成”,执行人也点了通过。但后来生产环境发现,有一批订单实际上没支付成功、却在页面上显示支付完成——原因是前端拿到支付回调的成功标志就展示了结果,而数据库里的支付状态更新失败了。这就是典型的只写了 UI 层、没写数据层的后果。从那以后我把支付类用例的期望结果统一改成“页面显示支付成功 + 数据库 ord_pay_status 字段更新为 2 + 支付流水表新增一条 pay_id 记录”,三层齐全,这类问题再没漏过。
期望结果里还要注意“可判定”这个细节:不要用“很快”“正常”“正确”这种主观词,要写可测量的指标。例如“登录响应时间不超过 3 秒”,而不是“登录速度很快”。执行人不需要有专业背景,看到描述就能给出明确结论,这才算合格。
如果你觉得每次写三层期望结果太啰嗦,我提供一个折中模板:“用户观察到的现象(UI) + 系统返回的数据(接口) + 关键数据的最终状态(库/存储)”。写的时候结合用例优先级:P0 用例写成三层,P1 至少写成两层,P2 可以只写 UI 层加轻量描述。
7. 自动化来了怎么办:用 Playwright 把用例变成可执行的断言
现在很多团队都在做自动化回归,Playwright 是前端测试里很热门的选择。但我想先说一个容易搞反的事:不是“把以前手动的用例转录成代码”,而是“按自动化的要求重新设计用例”。手动用例允许环境不稳定、允许步骤里有模糊描述、允许执行人“看情况判断”。自动化不行,机器没法“看情况”,它需要的是确定性的输入、确定性的步骤、确定性的断言。
7.1 手动用例转自动化的选择标准
先过一个筛选:哪些手动用例值得转自动化?我的标准三条——大概率会回归、核心业务路径、执行成本高。像登录、加购、支付、结算这种每个版本都要回归的路径,优先转。像“用户名字段长度 20 和 21 字符的边界比较”,执行成本也不高,但回归频率低,自动化优先级就往后放。
反而不建议自动化的类型:强依赖真实第三方数据的场景(除非你能稳定 mock)、需要人工视觉判断的审美类场景、每次执行都需要动态准备复杂数据的场景。硬转自动化只会得到一个三天两头因为环境原因挂掉的测试套件,维护成本远超收益。
7.2 用例标题直接成为测试用例名
转换时最省力的做法是保留手工用例的编号和标题,让自动化脚本和手工用例一一对应。例如登录模块的手动用例TC-LOGIN-002转成脚本就是:
// 对应手工用例 TC-LOGIN-002:密码错误时提示“用户名或密码错误” import { test, expect } from '@playwright/test'; test('TC-LOGIN-002 密码错误时提示“用户名或密码错误”', async ({ page }) => { await page.goto('https://example.com/login'); await page.getByLabel('用户名').fill('test_user'); await page.getByLabel('密码').fill('Wrong@123'); await page.getByRole('button', { name: '登录' }).click(); // 期望结果第一层:UI 层 await expect(page.getByText('用户名或密码错误')).toBeVisible(); await expect(page.getByLabel('用户名')).toHaveValue('test_user'); await expect(page.getByLabel('密码')).toHaveValue(''); // 期望结果第二层:接口层 const loginResponse = await page.waitForResponse('**/api/login'); expect(loginResponse.status()).toBe(401); });这段代码基本就是把第 6 章的高质量期望结果原样翻译成断言。如果手工用例的期望结果写得含糊,转自动化时你会卡在“到底该断言什么”上无从下手。反过来,期望结果写得具体,转自动化其实就是体力活。
7.3 自动化用例要对“稳定性”重新建模
手动用例里不太在意的细节,自动化里全是坑。比如:
- 数据独立性:自动化用例不能依赖手动测试留下的数据状态,每条用例要自己创建数据或清理数据。我一般用 API 直接预置数据,而不是靠 UI 一步步搭环境。
- 可重入性:用例执行失败后重跑,要保证能回到初始状态。所以前置条件里的“账号锁定”这种有状态的数据,自动化里要专门准备一个可以被锁定、也能被解锁的隔离测试账号。
- 等待策略:Playwright 自带自动等待,但 click 之前如果网络慢,还是可能触发“页面重定向导致的元素丢失”。遇到这种情况我会在关键跳转后用
expect(page).toHaveURL(...)确认当前页面,再去操作后面的元素。 - 网络异常流:手动用例里的“断网”场景,自动化里用
page.route()拦截并让请求失败来模拟。
稳定性优先于覆盖率。一个不稳定的自动化套件,在 CI 里天天误报,最终会被团队弃用。我的经验是:宁可维护 30 条跑 100 次都稳定的用例,也不要 100 条跑一次挂一次的用例——后者不仅没有减轻负担,反而让团队对自动化彻底失去信心。
8. 用例的资产管理:评审、维护,以及 Tessy 工具场景下的 Excel 整理术
写完了用例,是不是就万事大吉了?远没有。测试用例是资产,资产就要管理。我看到不少团队用例库用得乱七八糟,同一个需求有几十条一模一样的老用例,需求改了用例不更新,最后干脆没人看。这一章讲我怎么把用例库从“一次性文档”变成“活资产”。
8.1 用例评审:查的不是格式,是风险
用例写完后我建议再组织一轮评审,评审对象不一定只有测试,最好拉上产品和开发。评审时我主要看五件事:
- 有没有漏掉核心链路的关键分支(对着功能点拆分清单逐项打钩)。
- 期望结果是否三层齐全,是否可判定。
- 有没有重复用例,同一场景同一数据在多个用例里反复出现。
- 优先级标得是否合理,P0 用例是不是真的高影响面。
- 用例是否依赖一个不确定的外部环境(比如实时汇率、当前时间)。
评审不是过堂,目的是提前发现用例盲区。我见过最有效的评审方式是:让评审人随机挑三条用例,现场按用例执行一遍,不许看系统实现,只看用例描述能不能指导他判断结果。一次就能测出用例质量的大问题。
8.2 维护规则:用例跟着需求走,不跟着感觉走
需求变更是用例维护的主要驱动力。我的维护规则很简单:需求变更时,先把旧用例标记为“废弃”,再写新用例,不要在原用例里改来改去。因为当一条用例的标题、数据、期望结果有一半被改过的时候,它已经是一条新用例了,保留旧痕迹只会让历史追溯混乱。
执行频率统计也是一个好用的维护手段。用例管理工具里一般都有“最近三个月被执行次数”这种数据。执行次数为零且不在任何回归集合里的用例,基本可以标记废弃。用例库最怕的不是缺用例,而是堆了一堆没人敢删的僵尸用例——它们会淹没真正需要关注的用例,增加回归成本。
8.3 Tessy 等工具场景下,为什么 Excel 整理方式决定了导入效率
嵌入式测试领域常用 Tessy 这类工具做单元级测试用例,它的用例通常通过 Excel 表格批量导入。这个场景和 Web 测试完全不同:用例的对象不是页面,而是被测函数;期望结果不是 UI 现象,而是函数的输出参数、全局变量变化和桩函数行为。
我的同事们刚用 Tessy 时最常犯的错,是在 Excel 里把用例写得像功能测试报告。后面折腾几次后,我们固定了一套表头结构,按这个整理导入基本不报错:
| 列名 | 填写样例 | 说明 |
|---|---|---|
| TC_ID | TC_CALC_001 | 唯一用例标识,Tessy 用它做映射 |
| 被测函数 | int calc(int a, int b) | 函数签名,必须与工程中完全一致 |
| 输入参数 | a=1; b=2 | 分号分隔多个参数,变量名必须匹配 |
| 全局变量初值 | g_flag=0 | 被修改的全局变量在这里设初值 |
| 桩函数行为 | mock_net_send => return 0 | 配置桩函数返回值,按工具要求写 |
| 期望输出 | return=3; g_flag=1 | 预期返回值与关键全局变量终值 |
| 覆盖目标 | MC/DC | 可选,指定动态覆盖目标 |
这份表头用过之后,我总结了 Excel 整理的几条经验:
- 不要用合并单元格。Tessy 导入时合并单元格会造成数据错位,宁可在多行里重复写用例标题。
- 变量名与函数签名严格一致。Excel 里写的参数名和工程代码里不一致时,导入直接失败,这个报错排查起来非常痛苦。
- 桩函数配置单独一列,别杂在期望输出里。混在一起会让人分不清“这是我们要设的桩,还是期望的结果”。
- 每个用例独立一行,不要为了省事把多个输入组合塞进一个单元格。参数化不是让你把它们挤在一起,而是多行多用例。
嵌入式用例的维护和 Web 用例同理:函数签名一改,Excel 里的被测函数列要同步更新,否则下次导入就会拿到一片红线报错。建议每次提测前专门检查一次这两个列,能省掉整个下午的排障时间。
另外多说一句:别指望工具能替你设计用例。Tessy 能把 Excel 变成可执行的测试,但 Excel 里的内容有没有覆盖到函数的边界和异常分支,还得靠测试人员按第 4 章的方法去设计。工具只负责执行,设计依然是人的事。我自己带团队时,最常用来检验用例质量的一句话是:如果明天换一个完全没参与过需求的人来执行这份用例,他能不看代码、不问开发,就判断出系统行为对不对吗?如果能,这份用例就是合格的。这也是我给所有测试新人的第一个目标——先把“判断标准”写到别人照着做也能一眼看出毛病,再谈那些更花哨的东西。