# 测试到底在找什么：程序能跑通，不等于它做对了

从缺陷链、测试预言到验证与确认，讲清“测试通过”能得出什么结论、不能得出什么结论

> 测试基础 · 缺陷与失效 · 测试预言 · 约 6 分钟 · 09 月 19 日

## 本篇要点

1. 测试的核心动作只有三步：给输入、观察实际行为、与一个“预期”做比较；没有预期就无法判定对错。
2. 缺陷的来路是一条链：人犯错（error）→ 工作产品里留下缺陷（defect，即 bug）→ 执行时表现为失效（failure）；测试能直接观察到的只有失效。
3. 两个边界要记住：缺陷可以长期不产生失效（代码没被执行到就不暴露），失效也不一定来自缺陷（网络、依赖服务等同样会造成异常表现）。
4. “预期”由测试预言提供，来源可以是需求、旧版本行为、通行做法或人的判断；预言几乎不可能完整，它只盯着被设定去看的方面。
5. 因此用例通过只能说明“它没有在我盯着的地方失败”，不能说明程序没有缺陷、没测到的路径没问题、或预期本身是对的。
6. 需求文档本身也可能是缺陷：验证问“有没有把产品造对”，确认问“造的是不是对的产品”，后者靠比对需求文档发现不了。
7. Dijkstra 的结论成立，是因为可执行路径太多、测试只能覆盖极小一部分；再叠加预言可能不准，判定用的尺子本身也未必可靠。
8. 实践含义：每条用例都写预期结果、说明预期的依据、并分清自己是在做验证还是确认。

---

给自己代码把关的开发者，常常有这么一种“测过了”的体验：把功能从头点一遍，页面出来了、接口返回 200、数据库里也写进去了，于是觉得没问题了。但测试真正要回答的问题不是“它跑起来了吗”，而是“它做的事，和它该做的事一致吗”。这两句话差得很远。差在哪里，就是这篇要讲清楚的事。

## 测试的动作只有三步，关键是第三步

把一次测试拆开，只有三个动作：给程序一个输入，观察它实际做了什么，再把实际结果和一个预期结果做比较。不一致，就说明找到了问题。

最容易被忽略的是第三步。程序不会“自己发现错误”，它只是产出行为——返回一个值、写一条记录、弹一个提示。是“比较”这一步把行为变成了结论。没有预期，测试就无从判断：一个接口返回 `{"code": 0}`，如果你不知道它应该返回什么，这个 0 既不算对，也不算错。

所以测试的难点从来不只是“怎么把程序跑起来”，而是“凭什么说它现在这样是错的”。

## 缺陷是从哪儿来的

业界把问题的来龙去脉分成三段，串成一条链子 [1]：

```mermaid
flowchart LR
  A["人犯了错 error"] --> B["工作产品里留下缺陷 defect，也就是 bug"]
  B --> C["执行时表现为失效 failure"]
  C --> D["测试观察到这个失效"]
  D --> E["与预期比较后判定"]
```

- **错误**：人的一次失误。比如需求写的是“年满 18 周岁可注册”，开发者写成了 `age > 18`。
- **缺陷**：留在工作产品里的那个瑕疵。工作产品指的是开发过程中产出的任何东西，不只是代码，需求文档、设计文档、测试用例都算——一份含糊不清的需求描述，本身就是缺陷。
- **失效**：程序运行时表现出的、与预期不符的现象。用户输入 18 岁被拒绝注册，这就是失效。

为什么要费劲区分这三层？因为测试能直接观察到的只有第三层。你看到的是“18 岁被拒绝了”，看不到代码里那个 `>`。从失效倒推缺陷，是调查和调试的工作，不是测试本身。

围绕这条链，有两个反直觉的地方值得记住。

第一，缺陷可以长期不产生失效。没被执行到的那段代码里就算有缺陷，它也不会自己冒出来。

第二，失效不一定来自缺陷。网络断了、依赖的服务挂了，同样会表现为“程序不对”，代码里却可能一点瑕疵都没有。

## 预期从哪来：测试预言

第三步里那个“提供预期、用来判断对错的东西”，叫测试预言（test oracle）[3]。名字听着玄，其实就是判断依据：它回答“按什么标准，这次结果算对”。

来源可以有很多：需求文档里写死的规则、上一个版本的行为、同类产品的通行做法，或者有经验的人的判断。写用例时那句“预期结果：提示年龄不符合要求”，就是把预言固化下来，让它可复用、可争论。

真正的麻烦是：预言几乎不可能完整。它只会盯着自己被设定去看的那几个方面。断言写成“接口返回 200 且 code 为 0”时，提示文案写错、金额算错、慢得离谱，这条用例都不会响。所以更准确的说法是：通过一条用例，你得到的结论是“它没有在我盯着的地方失败”，而不是“它一定对” [4]。测试研究把“怎么判断实际结果是不是对的”称为预言问题（oracle problem），它至今仍是自动化测试里最难的环节之一。

## 需求本身也可能是缺陷

回到注册的例子。需求写着“年满 18 周岁可注册”，开发者照写，测试照测，全部通过。可如果真实业务规则其实是 16 岁呢？程序完全符合需求，上线后用户照样用不了。

这就要引入一对必须分开的词 [5]：

- **验证（verification）**：我们有没有把产品造对。对照的是需求、规格和设计文档。
- **确认（validation）**：我们造的是不是对的产品。对照的是真实的使用需要。

Boehm 的概括常被引用：verification 问的是“有没有把产品造对了”，validation 问的是“造的是不是对的产品”。两者谁也替代不了谁。日常说的“测试”，默认基本落在验证这一层；而真正致命的问题，常常出在确认这一层——它靠对着需求文档比对结果是发现不了的，必须回到需求本身、回到用户和业务方那里去问。

## 一条用例通过，到底说明了什么

把前面几节合起来，一条成功通过的用例，你最多能说：

> 在这一次运行里，用这组输入、这个环境，程序的行为没有偏离我用来比较的那个预期。

它不能说明的，至少有这几条：

- 其它输入没问题。你只试了这组输入。
- 没执行到的代码没问题。缺陷可以安静地待在无人踩到的路径里。
- 预期本身是对的。如果预期写错了，用例“通过”只说明程序和你犯了同一个错。
- 程序符合真实需要。那是确认层面的问题。

Dijkstra 在《Notes on Structured Programming》里写过一句被反复引用的话：程序测试可以用来证明 bug 的存在，但永远不能证明 bug 不存在 [2]。他的理由是，程序可能的执行路径太多，测试只能覆盖其中极小一部分，没走到的地方有没有问题，测试回答不了。而预言不完整是另一层原因：即使真的走了那条路径，你手里那把用来判定对错的尺子本身也可能是错的。

举个例子。需求是“金额输入框只允许两位小数”。你测了 0.01、1.99、100.00，全通过。你能说这个输入框没问题吗？不能。负数、极大值、空字符串、前后空格、全角数字、超长输入都没试过；而且“只允许两位小数”这条要求本身该不该允许负数，也从来没人检查过。前者是没覆盖到，后者是没验证预期本身。

## 落到手上的三个动作

**第一，每条用例都写下预期结果。** 只有操作步骤、没有预期结果的用例，产出的结论无法判定，本质上不算测试。

**第二，写完用例回头问一句：这个预期是哪来的？** 最靠得住的来源是需求或验收标准。如果依据只是“我觉得应该这样”，要么去找人确认，要么在缺陷单里把依据写清楚——依据站不住，报出来的问题就很难被接受。

**第三，分清自己在查哪一层。** 对照需求测功能，是验证；判断需求是否真的对，是确认。后者不是测试一个人能拍板的，但当你发现需求有歧义、有遗漏、和业务方的说法对不上时，应该提出来——需求文档里的缺陷和代码里的缺陷一样值钱，而且发现得越早越便宜。

下一篇会接着这里往下走：既然“预期”是判断对错的前提，那需求到底怎么一步步变成可以测的预期和测试点。

## 术语表

- 错误 / 缺陷 / 失效：人犯错是 error，留在代码或文档里的瑕疵是 defect（也叫 bug），程序运行时表现出的与预期不符是 failure；三者是一条因果链上的三个位置。
- 工作产品：开发过程中产出的任何东西，包括代码、需求文档、设计文档和测试用例；缺陷可以存在于其中任何一项里。
- 测试预言（test oracle）：判断一次测试结果算对还是算错的依据，通常来自需求、历史版本行为、通行做法或人的经验。
- 预言问题（oracle problem）：预期几乎不可能写完整，一个预言只覆盖它盯着的那几个方面，这使“测试通过”的结论天然带有边界。
- 验证与确认（verification / validation）：验证是“有没有把产品造对”（对照规格），确认是“造的是不是对的产品”（对照真实需要）。

## 来源

1. [ISTQB 术语表 — error / defect / failure 的定义与三者关系](https://glossary.istqb.org/)
2. [Dijkstra, Notes on Structured Programming (EWD249) — “测试只能证明 bug 存在”的出处](https://www.cs.utexas.edu/~EWD/transcriptions/EWD02xx/EWD249/EWD249.html)
3. [Test oracle — 测试预言的定义、来源分类与预言问题](https://en.wikipedia.org/wiki/Oracle_(software_testing)
4. [Barr et al., The Oracle Problem in Software Testing: A Survey — 预言不完整及其对测试结论的限制](https://earlbarr.com/publications/testoracles.pdf)
5. [CMU SEI — verification 与 validation 两种提问方式的解释](https://www.sei.cmu.edu/blog/incorporating-agile-principles-into-independent-verification-and-validation/)

---

原文：https://pangzhengboyin.com/articles/what-software-testing-actually-looks-for-d9e91b1f

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