上一篇留下一个悬念:预期是判断对错的前提,而预期最靠得住的来源是需求。真到动手时,最先撞上的问题不是“用例怎么写”,而是“需求就这一句话,我到底该验哪几件事”。

看一条很常见的需求:

已登录用户可以在结算页输入优惠码。有效优惠码按指定比例抵扣商品小计。每个优惠码每位用户限用一次。过期优惠码给出错误提示。抵扣后订单总额不得为负。

你打开页面,输入一个有效码,折扣出来了,就算测完了吗?这一句需求里至少藏着十来个要验的点。把它们找出来,是测试里一个单独的动作,它有名字,也有方法。

先把三个层次分开:依据、测试点、用例

新手最容易混的是这三个词,它们其实是三个不同的东西 123

  • 测试依据(test basis):你据以设计测试的那个“知识体”。常见的是需求文档、验收标准、接口约定、设计说明、用户故事,也可以是代码本身,甚至是团队里没有写下来、但大家都默认的口头共识。
  • 测试条件(test condition),中文里也常叫 测试点:从测试依据里识别出来的、可以被验证的一个方面。它只回答“要验什么”,不回答“怎么验”。
  • 测试用例:具体的前置条件、输入数据和预期结果。它回答“怎么验”。

从依据里把测试点找出来的这个动作,叫 测试分析(test analysis):读需求,然后识别出有哪些点需要验。它和分析完之后的“测试设计”是两件事——前者决定测什么,后者决定怎么测 6

这三个层次是漏斗形的:

绘制中

一份需求可以拆出几个到十几个测试点,一个测试点又能长出好几条用例。中间这一层不是为了好看,它有两个实在的用处。

第一是 核对覆盖。需求条目和测试点能一一对上,你才说得清“这次覆盖了多少”。跳过这一层,你手里只有一堆用例,漏没漏哪块谁也看不出来。

第二是 排优先级。测试点是“要验的事”,比用例好排序:可以先给每个点各派一条用例趟一遍,再对高风险的点补数据。真实项目里时间永远不够,这个顺序决定了先保什么。

第一步:把一句话拆成原子陈述

上面那条需求看起来只有五句话,但它其实由多条独立的规则拼起来。逐句拆:

需求里的原子规则由它导出的测试点(部分)
已登录用户可用优惠码未登录用户使用优惠码应被拒绝
按比例抵扣商品小计抵扣额按指定比例计算正确;抵扣只作用于商品小计
每位用户每码限用一次第二次使用被拒;换个用户用同一个码可以通过;换设备、换会话后再用仍被拒
过期码给出错误提示过期码不产生折扣,且提示内容正确
抵扣后总额不为负折扣超过小计时总额停在 0;小计为 0 时用码仍为 0

这一步的要点是:一个测试点对应一条可以被单独验证的规则,而不是一段需求原文。像“已登录用户”这种限定词,本身就意味着“未登录”是一个必须验的反面情形——它写在需求里,但很容易被读过去。

除了需求写明的,还有一类点需求从来没提,用户却默认它是对的:码的大小写、前后多打的空格、根本不存在的码、同一个请求被连点两次提交、抵扣服务超时。这些叫隐含条件,是经验和新手的差别所在。做法很简单:对每个测试点追问一句“还有什么可能出错”,通常在正向、反向、边界这三个方向上都问一遍就够了。

测试点还不是用例

拿“抵扣后订单总额不得为负”这个点展开,它自己不给数据,用例才给:

  • 小计 10 元,折扣 100%,预期总额 0 元;
  • 小计 10 元,折扣 150%,预期总额 0 元,而不是负数;
  • 小计 0 元,用任何有效码,预期总额 0 元。

这里要回到上一篇的那句话:每条用例都必须写出预期,而预期得有出处。上面这三条的出处在需求里写得很明白(“不得为负”),直接引用就行。如果需求没写、你又说不清依据,那这条用例的结论就站不住——这正是测试点分析要尽早做的原因,它在你写用例之前就把这类空洞暴露出来了。

需求写得含糊、残缺时怎么办

现实里的需求很少像上面那样整整齐齐。问题可以分三类:

  • 模糊:一句话能读出两种意思。比如“工作日 24 小时内到账”,是自然日还是工作日?“满 100 元免运费”,刚好 100 元算不算?
  • 缺失:根本没提。比如超时、重复提交、并发操作时系统该怎样。
  • 矛盾:两处规则打架。比如一处写“每人限用一次”,另一处写“同一账号每天可用三次”。

处理这类需求,有一套动作顺序。

第一步,不要替需求方做决定。 新手最常见的反应是“那我按合理的来”,自己选一种解释往下测。等测完一轮再发现理解偏了,这批用例基本全废——这是最贵的错。

第二步,把模糊点变成选择题问回去。 不要问“这条需求是什么意思”,对方很难回答;改问“工作日是指周一到周五、不含法定节假日,还是指自然日?”列出你想到的两种以上解释,让对方挑。对方一句话就能答完,也顺带证明你读懂了这条需求。这是整套动作里性价比最高的一招。

第三步,把答案落到文字上。 答案写在需求文档、需求单的评论或验收标准里,别停留在口头。因为它接下来要当预期用,将来会有人拿它来判断“这到底算不算 bug”。

第四步,实在问不到人的时候,把假设显式写下来。 假设写进用例的备注,同时在测试报告里单独列一节。这样一旦后来发现假设错了,靠需求条目和测试点之间的对应关系,你能很快圈出受影响的是哪几条用例,而不是全部重测。

第五步,需求本身的问题要报出来。 一条写不清楚的需求就是缺陷,而且是最早、最便宜就能修掉的那类:它还没变成代码,改一行字比改一行代码、比上线后打补丁都便宜。

最后做一次覆盖核对

把需求条目列在一边,测试点在另一边,连起来看两个方向:

  • 有需求、没有对应的测试点?要么你漏了,要么这条需求根本不可测(“系统应界面友好”“响应要快”这类没有可测量标准的话就属于此类)4
  • 有测试点、找不到对应的需求?可能是隐含条件,这很正常,但要标注它的出处,别让后面的人以为是凭空加的。

这个核对做完,你手上才算有一份“要验什么”的清单,而不是一堆散落的用例。下一步再挑测试设计方法(等价类、边界值那一类),把每个点变成具体的数据和预期。