测试:冲着坏消息去 — 三个悖论和一个算术
这一章讲四件事: 测试分几层、计划为什么必须先行;三个悖论(它们决定了测试的目的); 错误聚集定律(它决定测试的靶子);以及进度不够时那道选择题——书里算给你看, 为什么「把没测完的东西拼一起赌一把」几乎必输。
1. 先看现象:「测试全过了」之后的第三次宕机
测试报告写着:用例通过率 100%。上线第三周,系统还是挂了。
问题出在哪?出在「通过率」这个词给的安全感上。本章先给三个事实, 它们合起来解释了上面的事故,也决定了测试该怎么做:
- 测试跑过的输入,永远只是所有可能输入的极小一部分(后面给机制);
- 「用例都过了」度量的是用例,不是软件——用例没覆盖到的地方,过了等于没测;
- 漏测的缺陷不均匀分布——它们扎堆。
先立三个词。单元测试:对单个独立组件的测试;集成测试:对组件拼起来的组合的测试, 重点看接口(两块软件之间约定好的联系方式);系统测试:对整个系统、对照需求规格说明的测试1。
2. 顶层全景
测试的目的(三个悖论改写)
悖论1 测试只能证明缺陷存在,不能证明不存在
悖论2 成功的测试 = 发现了错误的测试
悖论3 零错误对价值什么也没说
│
▼
既然目的是「找」,靶子在哪?
错误聚集:一半错误在 15% 的模块;80% 在 2%(更狠的口径)
│
▼
怎么找:黑盒(按外部行为出题)+ 白盒(按代码内部路径出题)
非法输入 · 压力测到 x+1 · 用例必须写明期望结果
│
▼
什么时候算完:数「每周新发现错误率」或「埋的 bug 找回多少」,
不数「用例通过率」(那是无效指标)
│
▼
进度不够时的选择题:承认落后 vs 大爆炸集成(主走查看算术)
图说:每一层都被上一层逼出来:目的变了,靶子、打法、完成标准全跟着变。
3. 核心原理
3.1 计划先行,并且每一层都能追回需求
计划先行(原则 108):别等软件写完才挠头「这玩意怎么测」。 顺序是倒着的:需求定稿前,测试计划员就要从「可测试性」角度审需求;集成测试计划在初步设计定稿前; 单元测试计划在详细设计一完成就动工2。
追回需求(原则 107):再建一张第 05 章 5.2 那样的二维表,这次行=测试用例,列=需求: 交叉格画 1 表示「此用例验证此需求」3。查法同款:
- 横查: 一整行没画 1 → 这条测试没有目的,纯属仪式;
- 竖查: 一整列没画 1 → 那条需求没被任何测试覆盖(漏测)。
排序还免费送:需求的优先级(第 04 章 3.4 的 M/D/O)取最大值,就是对应测试的优先级3。
3.2 主走查:进度不够时,那道选择题的算术
原则 119 的场景每个项目都遇过,书里把账全算好了,整段走完4:
设定: 按计划,单元测试、集成测试、系统测试各 2 个月,共 6 个月。 现在距交付日只剩 1 个月,而单元测试只完成了 50%。
第一步:算落后多少。 单元测试还差 1 个月,集成测试 2 个月、系统测试 2 个月没动—— 按正常顺序,你已经落后 5 个月。
第二步:摆出两个选项。
- 选项一:向客户承认 5 个月延误,请求推迟;
- 选项二:把所有组件直接拼成整体(包括那 50% 没做单元测试的),期盼奇迹——书里给这个机会的 概率(可能性大小,用百分数表示)量级是 0.001%。
第三步:看选项二实际发生什么。 拼起来的系统必然不跑,然后你要在接口错误和 没测过的组件错误之间猜是哪个——书里原则 123 点破:大量时间会耗在「确定哪一个是真 正的原因」上5。 作者的判断:选二的后果通常是排期再延 6 个月——比选项一还多赔 1 个月4。
为什么经理总选二? 书里说得刻薄而准确:因为选二「看起来像在承认失败之前竭尽全力」4。 这是给管理者的旗语,也是给工程师的:不能靠跳过单元测试和集成测试来节省时间。
3.3 悖论一:测试只能证明「有」,不能证明「没有」
Dijkstra 那句被书里郑重引用的话:无论测得多彻底,测试只能揭示缺陷存在,不能确保缺陷不存在; 想「证明正确」,要的是完全不同的另一种方法(正确性证明)6。
机制在哪?原则 115 顺带给了:测试只能动用输入域里极小一部分可能的值7。 一个收两个整数的程序,可能的输入已经是天文数字;收一段文本的程序,可能输入比宇宙原子还多。 跑一百万个用例,等于在无边的海里舀了一瓢——瓢里没鱼,不证明海里没鱼。
由此直接推出完成标准不能是「测够了就证明没问题」(3.7)。
3.4 悖论二:「测试成功」的意思是「找到了错误」
原则 113 用了一个完整的类比,值得整段搬进脑子8:
你病了,医生把你的血样送去化验。几天后医生来电:「好消息!你的血液正常。」 ——这不是好消息。你病着,否则不会去化验。一次成功的化验,是查出了你哪里有问题的那次。
软件同理:程序里有缺陷(否则不用测),所以成功的测试=发现了缺陷的测试。 「好消息!测试跑通了」是错误的态度。这个立场立刻推出两条纪律:
- 别测自己写的软件(原则 109):测试期间的正确心态是「盼着暴露缺陷」, 而开发者对自己孩子的心理恰恰相反——带着「不想发现问题」的偏见,测试必然变松9; 独立测试员至少接管:集成测试、系统测试、以及「单元测试做得够不够」的复核;
- 连测试计划也别让自己的开发者写(原则 110):写软件时做错的那个假设 (比如「输入不会超过 100」),写测试时会原样再犯一次——出题人不能是答题人10。
书里还留了一个诚实的括号:Cleanroom 学派有相反主张,且今天也有让开发者自测的思潮 (中文版译者注也点了这一点)——但「盼坏消息」的心理机制没变9。
3.5 悖论三:零错误不等于有价值
温伯格的「无差错谬论」,第 02 章已经引过,这里给它在测试语境的完整形态11: 大量错误当然证明软件没用;但零错误对价值什么也没说—— 如果你开发的根本不是客户要的系统,那么形式化方法、全部测试、全部产品保证加起来都是浪费。 这条悖论把测试放回了它的位置:测试管「把东西做对」,不管「做的是对的东西」——后者是第 03/04 章的管辖。
3.6 靶子:错误扎堆,而且越扎越紧
原则 114 给了三档数字,一档比一档惊人12:
大型系统中,约一半错误出现在 15% 的模块里;80% 的错误出现在 50% 的模块里。 Okimoto 与温伯格的口径更狠:80% 的错误集中在 2% 的模块里。
推论直接可执行:测试日志(逐条记录「何时、在哪个模块、发现了什么错误」的流水) 不仅记「这个月发现多少错误」,还要记「每个模块各发现多少」; 某个模块历史上持续多产错误,别再修了——从头重写,按第 06 章的简单标准写,别按「聪明」写12。 这条到第 12 章还会再硬一次:发布前错误多的模块,发布后错误也多。
3.7 打法四件套与「什么时候算完」
黑盒+白盒(原则 115):黑盒测试只看外部行为出题(输入该得什么输出); 白盒测试扒开代码、按内部路径出题(比如「50 条指令以内的程序所有路径都要走到」)。 书里的例子精确展示为什么两个都要13:
需求说「打印输入列表中所有数字的总和」。程序员埋了一个后门:一旦输入里出现 213,总和清零。 黑盒测不到它——需求里没有 213,除非碰巧随机抽中含 213 的用例; 白盒要求路径覆盖,那个「等于 213」的分支必须被走到,几乎必然现形。
非法输入(原则 117):给 0~100 范围的整数列表排序,别只测正常列表—— 负数、全部相等的数、非整数、字符、空记录,全都该有专门用例14。
压力(原则 118):需求写「每小时最多 x 个」,就测到 x+1、x+2—— 系统管不住环境,别在环境失灵的那天崩盘(和第 04 章 3.6 的「101 架飞机」同一件事)15。
期望结果(原则 116):每个用例必须写明期望输出。不写,测试员无法判定成败, 还会潜意识把错的判成对的(盼着它对);更糟的是把对的判成错的,一队人去「修」正确的代码16。
什么时候算完(原则 121):「时间到了」是政治答案不是质量答案。有效的完成度指标 (用来衡量某件事的数)有两个:每周新发现错误率(该降下来);bebugging—— 故意往代码里埋 20 个已知缺陷(数是演示编的),测试找到 15 个 → 估计真实缺陷也被找到了约 75%, 还剩约四分之一没被这套测试触到,而且埋的那 5 个正好暴露测试的盲区17。 书里点名了一个无效指标:「用例通过率」——除非能证明用例覆盖了需求,通过率说明不了软件质量17。 (补一嘴机制:覆盖率度量——语句/分支/路径覆盖率,即被跑到的代码行、岔路、全程路径的百分比—— 是工具可以自动统计的,但它们同样只证明「走到了」,不证明「走对了」18。)
配套三小件:在软件里埋测量探针(记录执行轨迹、异常、调用)以便失败后取证19; 每个错误分析原因——技术原因之外还有管理原因(「本该在集成测试前手动核对一次」),并告知全员20; 对错不对人:写软件要求的细致程度没人能达到,错误是学习材料,不是罪证21。 书里另有一条文化层的:日本业界把产品缺陷视为公司之耻、工程师之耻,被指出错误应心存感激并广而告之22。
4. 作者的判断与证据
| 说法 | 谁的 | 证据 |
|---|---|---|
| 测试只证存在不证不存在 | 作者引 Dijkstra6 | 论证(输入域不可穷尽);无实验 |
| 一半错误在 15% 模块/80% 在 2% | 作者引 Endres、Okimoto/温伯格12 | 多份实测统计,口径间有差异 |
| 审查比测试更能发现错误 | 第 07 章引 Fagan 的 82% | 实测;与本章互为表里 |
| 大集成再拖 6 个月 | 作者的经验判断4 | 案例推算;「0.001%」是修辞量级 |
| McCabe 指标好用(1995)→「不再有用,对不起汤姆」(2021) | 作者本人两版自述23 | 2021 版亲手撤销;「我知道没有人再使用它了」 |
判断(我们的,不是书里的): 把 3.6 的聚集定律和 3.1 的追溯表合起来,现代测试策略可以压成一句: 按历史错误分布分配测试预算,按需求优先级分配覆盖义务——前者告诉你火力往哪集中(那 2% 的模块), 后者告诉你底线在哪(每条高优先级需求至少一个用例)。只用其中一张表,都会把资源撒平。 如果错,会错在: 如果这是一个全新系统,没有「历史错误分布」可言,聚集定律暂时无处落脚—— 此时唯一可用的是需求优先级那张表,聚集表要等第一轮数据回来才有资格说话。
5. 边界与局限
- 「别测自己的软件」在 2020 年代被广泛松动(作者书里就引了 Cleanroom 的反例9; 中文版译者注也补充:现在的倾向是程序员自测)。松动成立的前提是开发者真能切换到「盼坏消息」的心态; 判据看结果:自己的模块是不是总在集成阶段才暴雷。
- bebugging 今天很少真做17——埋缺陷的成本和心理阻力都大;现代等价物是「突变测试」(工具自动制造变体), 书里没有提,算我们补的背景。
- McCabe 指标已被作者撤回23:书里的圆数公式(把程序画成图,数节点和边)作为历史看看即可; 它想解决的问题(估计测试难 度)今天由覆盖率工具接管。
- 大爆炸的算术默认各测试阶段不可压缩、不可并行。现实中边界情况可并行一小部分, 但「跳过单元测试直接集成」没有并行红利,只有重复排障的利息——结论不变。
6. 可带走的
- 测试的目的是找坏消息:成功的测试=发现了错误的测试;「跑通了」本身不是好消息;
- 测试永远不能证明无缺陷——瓢里没鱼不证明海里没鱼;别拿「测过了」当安全声明;
- 零缺陷对价值什么也没说:做错了系统,零错误只是精准地白干;
- 错误扎堆:一半错误在 15% 的模块;按模块记错误账,常错的模块重写而不是再修;
- 黑盒+白盒缺一不可:埋进代码的「213 后门」,只有白盒的路径要求能抓住;
- 用例必须写明期望结果;非法输入、压力上限 x+1 都要有专门用例;
- 测试让独立的人做、测试计划让独立的人写——出题人不能是答题人;
- 完成度数「每周新错误发现率」或埋雷找回率,不数「用例通过率」;
- 进度不够时,承认 5 个月延误,好过赌 0.001% 的大集成再赔 6 个月。
7. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 三层测试定义 | 第6章 测试原则 | text/18-ch06.txt:9(搜「单元测试」) · text/18-ch06.txt:11(搜「集成测试」) |
| 测试追溯需求(横查/竖查) | 第6章 测试原则 | text/18-ch06.txt:29(搜「漏测」) |
| 测试计划先行(可测试性) | 第6章 测试原则 | text/18-ch06.txt:39(搜「可测试性」) |
| 别测自己的软件 | 第6章 测试原则 | text/18-ch06.txt:63(搜「自己的软件」) |
| 别写自己的测试计划 | 第6章 测试原则 | text/18-ch06.txt:67(搜「相同的错误」) |
| 只证存在不证不存在 | 第6章 测试原则 | text/18-ch06.txt:81(搜「正确性」) |
| 无差错谬论 | 第6章 测试原则 | text/18-ch06.txt:89(搜「无差错谬论」) |
| 成功的测试发现错误(验血) | 第6章 测试原则 | text/18-ch06.txt:103(搜「血样」) |
| 错误聚集(15%/2%) | 第6章 测试原则 | text/18-ch06.txt:113(搜「15%」) · text/18-ch06.txt:113(搜「仅仅2%」) |
| 黑盒白盒(213 后门) | 第6章 测试原则 | text/18-ch06.txt:125(搜「213」) |
| 用例含期望结果 | 第6章 测试原则 | text/18-ch06.txt:133(搜「期望的正确结果」) |
| 非法输入 | 第6章 测试原则 | text/18-ch06.txt:145(搜「负数」) |
| 压力测试 x+1 | 第6章 测试原则 | text/18-ch06.txt:155(搜「x+1」) |
| 大爆炸算术(5 个月/0.001%) | 第6章 测试原则 | text/18-ch06.txt:163(搜「5个月」) · text/18-ch06.txt:169(搜「0.001%」) |
| McCabe 指标与曲奇刀 | 第6章 测试原则 | text/18-ch06.txt:177(搜「曲奇」) |
| 完成度指标与 bebugging | 第6章 测试原则 | text/18-ch06.txt:199(搜「bebugging」) |
| 覆盖率三种 | 第6章 测试原则 | text/18-ch06.txt:211(搜「语句覆盖率」) |
| 单元测试前别集成(脚手架) | 第6章 测试原则 | text/18-ch06.txt:227(搜「脚手架」) |
| 埋测量探针 | 第6章 测试原则 | text/18-ch06.txt:235(搜「执行轨迹」) |
| 分析错误原因(含管理原因) | 第6章 测试原则 | text/18-ch06.txt:245(搜「管理问题」) |
| 对错不对人 | 第6章 测试原则 | text/18-ch06.txt:253(搜「学习经历」) |
| 被指出错误应感激(日式) | 第2章 一般原则 | text/14-ch02.txt:287(搜「心存感激」) |
| 2021:McCabe 撤回 | 作者序 | text/08-fm.txt:57(搜「对不起」) |
| 2021:测量已自动化 | 作者序 | text/08-fm.txt:59(搜「自动」) |