# 一条测试用例要写清什么：为什么“正常显示”不算预期结果

讲清前置条件、操作步骤、预期结果各自要写到什么程度，一条用例该覆盖多少，以及为什么预期结果必须在执行前定义

> 测试用例设计 · 测试分析 · 测试预言 · 约 9 分钟 · 09 月 19 日

## 本篇要点

1. 一条用例要把起点、动作、判定标准三样都写清：前置条件固定起点，步骤给出可执行的动作，预期结果给出无需判断的判定标准；缺了预期结果，手里有的只是操作说明而不是用例。
2. 前置条件要覆盖账号与权限、已有数据、环境开关，以及上一条用例留下的状态；判据是执行的人能照它把系统摆到起点、不必回头问你。
3. 步骤应一步一个可观察的动作，并使用界面上的确切文案而不是自己的概括；当“不做什么”是关键时（如留空）也要明确写出来。
4. “正常显示”“按预期工作”不算预期结果，因为它把判定权交还给执行人；判定尺子不稳定，历史结果就失去意义，而且提示文字写错、金额差一分、提示成功但数据没入库这类缺陷都会算作通过。
5. 预期结果必须在执行前定义，否则可能把实际发生的错误结果填写成“预期”并接受下来；一份好的预期结果要写清确切文案、页面去向、数据的变化，以及不该发生什么。
6. 一条用例默认只承载一个独立结论：塞进多个独立结果后，失败时无法定位，第一个不通过还可能让后面的检查没跑到；操作判据是把标题读出来，需要用“而且”连接两件事就该拆。
7. 测试点和用例不是一对一：一个测试点可以派生多条用例，每条用一个具体数据去踩它，用例条数变多不等于需求变多。
8. 这套写法的代价是维护成本，适用于需要复现和交接的用例；高风险、要进回归集的用例写细，只给自己看一眼、跑完不再用的检查写要点即可。

---

上一篇把无穷多的取值压成了七条用例。那张表已经能交给别人执行了，但真拿到手上，执行的人马上会追问：账号是什么状态？购物车里有没有东西？输完之后，屏幕上出现什么才算这条过了？

这篇回答的就是这些追问。一条用例光有“取什么值”不够，它还得写清起点、动作和判定标准，才能真正被别人跑一遍并且得出同一个结论。

## 用例是三块拼图：起点、动作、判定标准

ISTQB 给测试用例（test case）的定义是：为某个特定目标或测试条件而设计的一组输入值、执行前置条件、预期结果和执行后置条件 [1]。换句话说，它要回答三个问题——

- 开始跑之前，系统处在什么状态？（前置条件）
- 我具体做什么？（操作步骤）
- 做完之后看见什么，才算这条过了？（预期结果）

这三个问题对应测试这件事的底层结构：测试不能只“操作一下看看”，它必须在操作之前就知道“什么事情算对”。少了第三块，你手里有的只是一份操作说明，不是一条用例 [2]。

## 前置条件：把起点钉死，别让别人来猜

前置条件（precondition）是执行这条用例之前系统必须已经满足的状态。它的作用很具体：把起点固定住，让不同的人跑出来是同一个结果。

为什么它经常被忽略又经常出事？因为同一串操作，起点不同结果就不同。用“管理员把用户从 Agent 改成 Manager”这条用例举例：如果账号权限、被改的那个用户当前的角色、是否已经登录过都没写清，另一个人按步骤点一遍，可能改的是一个权限不同的用户，也可能因为权限不足根本进不去后台。这时候他既不能判定通过，也不能判定失败——他卡在了起点上。这类“无法复现”的麻烦，常见源头就是前置条件缺失或含糊 [5]。

前置条件该写哪些东西？可以按下面几类过一遍：

- **账号与权限**：用哪个角色的账号登录，这个账号有没有权限进这个页面；
- **已有数据**：订单里有没有商品、金额多少，优惠码是什么状态，被操作的对象当前是什么值；
- **环境与开关**：在哪个环境、哪个浏览器；有没有依赖某个功能开关、某条配置；
- **上一条用例留下的状态**：如果这条用例依赖前一条的结果，必须写明。

最后一类在正式的用例规格模板里也有它的位置，IEEE 829 就专门要求记录这类用例之间的依赖关系 [4]。判断标准很简单：**照着你写的前置条件，能不能一步步把系统摆到起点状态。**如果执行的人需要回头来问你，就说明还缺东西。

## 步骤：一步一个能看见的动作

步骤是执行的人照着做的指令。它有两条要求。

**第一条，一步只放一个动作。**“登录并进入设置页”是两个动作，写成一个步骤，出问题时你不知道是登录挂了还是跳转挂了。“导航到设置”这种写法更麻烦——它要求执行的人自己理解“设置”在哪儿，不同的人会点不同的路径 [5]。

**第二条，用界面上的确切文字，别用自己的概括。**写“点击‘保存’”比写“提交表单”好，写“点击‘继续结算’按钮”比写“进入下一步”好。理由是界面上的文案就是一个可观察的事实，而“下一步”是你的理解。这一点在把用例交给别人、或者以后要改写成自动化脚本的时候尤其明显：自动化工具只会照字面执行，含糊的步骤让它无从下手。

还有一个容易漏的情况：**当“不做什么”是关键时，也要写出来。**比如“在邮箱地址一栏留空，不输入任何内容”。留空本身是一个动作，不写清楚，执行的人很可能顺手填了。

如果一组用例都要先登录，把这段收进前置条件或公共步骤就行。要控制的是“可复现”这条底线，不是字数。

## 预期结果：判定权不能落在执行人手里

现在说这篇的重点。为什么“正常显示”“页面加载成功”“系统按预期工作”不算预期结果？

**因为这三句话把判定权交还给了执行的人。**他看到页面之后，要自己决定“这算不算正常”。而不同的人对“正常”的标准不一样，于是同一条用例，在不同人手里、甚至同一个人不同时间跑，会得出不同的通过/失败结论。一份用例的结果一旦不稳定，它留下来的记录也就没了意义——你没法从“上次通过、这次失败”里读出任何信息，因为两次的判定尺子不一样 [5]。

**更要紧的是，缺陷正好藏在“看上去正常”的缝隙里。**提示文字写错了一个字、金额少算了一分、页面弹出“保存成功”但数据其实没入库、跳转对了但列表里没出现新记录——这些画面在“正常显示”这四个字底下全都算通过。预期结果写得越粗，能判定出来的缺陷就越少。

**还有一层原因，是测试预言的问题。**如果你不在执行前写下预期结果，而是先看系统实际做了什么、再回头把那个结果填写成“预期”，那你就等于宣布“凡是发生的就是应该发生的”。ISTQB 的说明很直接：预期结果应当在执行之前定义，否则一个貌似合理、其实是错误的结果，很可能被当成正确结果接受下来 [3]。所以预期结果不是执行完之后补的注释，它是用例的一部分，先写。

那一条好的预期结果长什么样？它要能被对照着看，具体到执行的人不需要做判断 [5]。可以从这几个角度写：

- **屏幕上出现什么**：确切文案，比如红色提示“抵扣比例需为 1 到 100 的整数”，出现在哪个字段下方；
- **停在哪儿**：页面是否跳转、跳转到哪个页面；
- **数据变成什么**：这条记录的值、订单总额、列表里多出的一行。数据的变化往往比界面更能说明问题；
- **不该发生什么**：表单没有提交、没有生成新订单、原数据没有被改动。

把“不该发生什么”写出来是很有价值的一步。很多缺陷的表现恰恰是“该做的没做”，而如果你只写了“应该出现什么”，执行的人看到提示出来了就判定通过，不会再去确认数据到底存没存。

## 一条用例该覆盖多少

回到上一篇那个抵扣比例字段（1 到 100 的整数）。等价类加边界值给出了七八个要测的值，那么“输入 0 被拒绝”“输入 101 被拒绝”“输入 abc 被拒绝”应该写成三条用例，还是一条？

**默认答案是三条。**ISTQB 的定义里，一条用例是“为某个特定目标”设计的 [1]。如果一个用例里塞进三个彼此独立的结果，会有两个后果：一是它失败时，你只知道“这条坏了”，不知道是哪个输入坏在了哪一步；二是执行的人常常在第一个检查点不通过时就停下来报错，后面的检查根本没跑到，等于白丢了信息 [6]。

这里有个很好用的操作判据：**把用例标题读出来。如果需要用“而且”来连接两件事，就该拆。**“输入 0 被拒绝，而且输入 abc 被拒绝”听上去就是两条用例。反过来，“提示文字正确，而且输入框标红”是同一件事的两个侧面，可以留在一条里——它们描述的是同一个结论，不是一个通过、一个失败各自独立 [6]。

再补一句和上一篇的衔接：**测试点和用例不是一对一。**“抵扣比例越界应被拒绝”是一个测试点，它可以派生多条用例，每条用一个具体数据去踩它。测试点说的是要验什么，用例说的是用什么数据、怎么做、验到什么算过。所以用例条数变多，不代表需求变多了。

那什么时候可以少写几条？如果这些检查点是同一结论的不同侧面，或者成本很高（比如每次都涉及一笔真实支付），把它们合进一条用例是合理的取舍。但只要它们可能各自独立地坏掉，就分开。

## 走一遍：把那条用例写出来

还是用优惠码需求。假设后台可以配置某个优惠码的抵扣比例，规则是 1 到 100 的整数，越界拒绝保存。上一篇已经定下要取 `0` 这个下界外侧的值。写成一条用例就是这样：

| 项目 | 内容 |
| --- | --- |
| ID | TC-DISCOUNT-002 |
| 标题 | 抵扣比例输入 0 时保存被拒绝，原比例不变 |
| 前置条件 | 以管理员账号登录后台；已存在优惠码 SUMMER20，当前抵扣比例为 20；该账号有优惠码配置权限 |
| 步骤 | 1. 打开 SUMMER20 的编辑页；2. 将“抵扣比例”字段清空，输入 `0`；3. 点击“保存”按钮；4. 刷新当前页面 |
| 预期结果 | 第 3 步后页面停留在编辑页，不跳转；出现红色提示“抵扣比例需为 1 到 100 的整数”；第 4 步刷新后，SUMMER20 的抵扣比例仍为 20，未被改成 0 |
| 后置条件 | 无数据变更，无需清理 |

对照一下常见的坏版本：

> 标题：优惠码测试
> 前置条件：（空）
> 步骤：1. 试几个值看看有没有报错
> 预期结果：显示正常

两个版本的差别不在于篇幅，而在于**能不能被别人照着跑出同一个结论**。坏版本没有起点，步骤里“试几个值”是执行人自己决定的，判定标准也落在他的判断里。这样的用例写在文档里，看起来覆盖率是满的，实际上什么也没锁住。

## 写多细，停在哪里

最后说清楚这套写法的代价，免得把它用成负担。

这套写法锁定的是**需要复现、需要交接**的场景：要交给别人的用例、进回归集的用例、以后可能被改写成自动化脚本的用例。它换来的是别人能独立执行、结果能比较、缺陷能被稳定复现。

代价是维护成本：界面文案一改，步骤和预期结果里的文字就得跟着改。所以不必所有用例都写到同一种颗粒度。高风险、要交接、要进回归集的用例写细；只给自己看一眼、跑完就不再用的检查，写要点就够。

至于用例执行之后怎么把失败写成一份别人看得懂的缺陷报告，那是下一篇的事。

## 术语表

- 测试用例（test case）：为某个特定目标设计的一组输入值、前置条件、预期结果和后置条件，能让人照着跑并得出通过或失败。
- 前置条件（precondition）：执行用例前系统必须已经满足的状态，作用是固定起点，让别人跑出同样的结果。
- 预期结果（expected result）：执行前就定好的、无需判断就能对照的判定标准，包含界面文案、页面去向、数据变化和不该发生的事。
- 后置条件（postcondition）：用例跑完后系统应处的状态，用于标记是否需要清理或衔接下一条用例。
- 一条用例一个结论：把彼此可能独立失败的检查点拆成多条用例，判据是用例标题是否需要“而且”来连接。

## 来源

1. [ISTQB Glossary: Test Case — 测试用例的定义（输入值、执行前置条件、预期结果、后置条件）](https://istqb-glossary.page/test-case/)
2. [ISTQB Glossary: Test Case Specification — 用例规格说明应包含的字段](https://istqb-glossary.page/test-case-specification/)
3. [ISTQB: The Test Development Process — 预期结果应在执行前定义](https://istqbfoundation.wordpress.com/2017/09/18/the-test-development-process/)
4. [IEEE 829-1998 Test Case Specification Template — 输入规格、输出规格、环境需求与用例间依赖](https://www.stickyminds.com/sites/default/files/article/file/2013/Software%20Test%20Case%20Specification%20Template.pdf)
5. [How to Write Test Cases That Catch Bugs — 用例五要素与常见写法缺陷](https://app.evaficy.com/how-to-write-test-cases)
6. [One Concept Per Test — 用标题中是否需要 and 判断是否拆分用例](https://prickles.org/tenet/one-concept-per-test/TS8)

---

原文：https://pangzhengboyin.com/articles/how-to-write-a-test-case-a2406fd4

> **庞征博引** · 想学的，慢慢都会
>
> 庞征博引是把想学的东西写成连载的 AI 学习工具。说出想学什么，它会先了解你的基础，再把主题写成一篇篇 5–10 分钟能读完的文章；边读边问，接下来学什么跟着你走。这篇就是这样写出来的。
>
> 开始你自己的连载 → https://pangzhengboyin.com
