专业书籍精读 · 持续交付 · 第 8 章
Continuous Delivery · Ch 8 · Jez Humble & David Farley · 2010
你在手机上下单、付款、收到一条确认短信——这一整串事情到底走没走通,得有人检查。上一章那道关只检查零件:每个小零件单独看都没毛病。这一章讲下一道关:把整台机器装起来,从头到尾走一遍,看用户真正想要的那件事有没有做成。
像新房验收。查水泥标号、量钢筋直径,那是零件检查;验收是另一回事——挨个拧开水龙头看出不出水,按下每个开关看灯亮不亮,关上门看锁不锁得住。零件全合格但马桶不冲水,房子照样不能住。
反直觉的地方在这儿:这份验收清单最值钱的时刻,不是装修完那天,而是开工之前。清单一列出来,双方当场发现「你说的封阳台和我理解的不是一回事」——这时候改只是改一句话,等墙砌好再改,就得砸墙。
过去这活全靠人干:一屋子人拿着清单一条条手工点。麻烦在于每改一个地方,理论上整张清单都得重点一遍——你动了付款按钮,谁敢保证注册流程没被带坏?人工点一轮要好几天,于是团队只好少发几次版;少发版就攒下更多改动,攒得越多越容易出事,绕回上上章那个死循环。
而且人做重复检查会累、会跳、会想「上次没问题这次应该也行」。最枯燥的活,恰恰最不该交给人。
第一,把验收清单写成机器能跑的东西,而且用业务的话写。「一个新客户下了一单,付款成功后,他应该收到一封确认信」——这一句话本身既是需求,也是测试。写它的时候客户、分析、开发、测试都在场,含糊的地方当场就暴露了。
第二,也是这一章真正的手艺:把「要检查什么」和「具体怎么点」彻底分成两层。上面几千条清单只说「登录、下单、付款」;最底下单独留一层,专门知道登录按钮长在哪儿、叫什么名字。按钮挪了位置,只改最底下那一层的一句话,上面几千条一个字都不用动。绝大多数团队的验收测试之所以贵到养不起,就是没切这一刀。
第三,能不隔着界面点,就别隔着界面点。走界面又慢又容易被一点点排版改动绊倒;绕到界面背后直接跟系统说话,同一件事快十倍,也稳得多。
最大的好处是让人敢改代码:改完把整套业务流程重跑一遍,全绿,你就敢按发布键。它真正的身份不是「找 bug 的工具」,是一张让人敢动手的安全网。
当然它有代价:这类检查天生又慢又贵,跑一轮几十分钟起步,所以它永远只能是少数——绝大多数检查该在上一关用便宜手段做完。
验收测试回答的不是「代码写对了吗」,而是「用户要的那件事,做出来了吗」;它能不能养得活,取决于你有没有把「验什么」和「怎么点」分开。
想看那两层具体怎么切、为什么不能录制回放、几千条端到端测试真跑得动吗? → 切到精读版
验收测试(acceptance test)问的不是「代码写对了没有」,而是「用户要的价值有没有交付」。反直觉的是:这一章名义上讲测试,实质上讲需求——它最大的一笔收益发生在写代码之前,在分析师、测试、开发和客户一起把「怎样算做完」写成一条条可执行的验收标准(acceptance criteria)的那个小时里。而它能不能活过三个月,只取决于一件事:你有没有把「验什么」和「怎么点」拆成不同的层。没拆,几千条测试会被界面的每一次微调拖垮,团队最后集体决定弃用;拆了,同样几千条能跑十年。
下单(客户A, 100元)。本章在 Part II「部署流水线」里紧接 Ch7:Ch5 画出了从提交到生产的整条传送带,Ch7 放大第一格(提交阶段),本章放大第二格——制品通过提交阶段之后要走的那道验收关。往上,它继承 Ch4 测试策略的四象限,专门承接「业务面 · 支持团队」那一格;往下,Ch9 接手非功能验收(容量、安全),Ch12 展开这里只点了一下的测试数据管理。现实里它对应的是:Cucumber / FitNesse / Concordion 那类工具,Selenium 与它的后代 WebDriver、Playwright、Cypress,以及 AWS 流水线里那个「尽可能像生产」的 gamma 环境。
提交阶段绿了,只说明「这次改动没有明显砸锅」。它完全没有回答「用户要的东西做出来了没有」——单元测试是开发者写给自己的,它验证的是「代码按我想的方式运行」,而不是「我想的方式是对的」。这两者的差距,正是返工成本最高的一类缺陷:功能做完了、代码也没 bug,但做的不是客户要的那件事。
旧世界靠人工验收补这个缺口,算笔账就知道为什么撑不住:一个中等规模的业务系统主干场景大约 150~250 个,人工完整回归一轮通常要 2 名测试跑 2~3 天。一年发 12 次版是 50~70 人日;而持续交付的目标是随时可发布,一年发几百次——人工回归在这个频率下不是贵,是算术上不可能。更糟的是它的失效方式:人做第 5 遍重复检查时的注意力远不如第 1 遍,手工回归恰恰是「越需要它可靠时越不可靠」的那类活。
本章还必须回应一个很硬的反对意见,作者开篇就把它摆了出来:「验收测试太贵、写完维护不起」。作者的回答不是否认,而是承认——写得不对的验收测试确实贵到养不起——然后用整章篇幅讲怎么写才不贵。所以这一章的真正主题是可维护性,不是覆盖率。
本章第一个、也最容易被跳过的主张:验收标准必须在这条需求开工之前写出来,而且由分析师、测试、开发、客户一起写——它同时是需求说明、验收清单,以及最终那段自动化测试的骨架。为什么坚持「开工前」?因为写验收标准的过程本身就是一次高强度的需求澄清。当你被迫把「支持优惠券」写成「给定一张满 100 减 20 的券、当客户下了一笔 95 元的单、那么券不可用且提示还差 5 元」时,含糊立刻现形。这类分歧在开工前发现,成本是改一句话;等功能做完再发现,成本是返工。本章最大的一笔收益,发生在任何测试代码被写下之前。
与之配套的是责任归属:本章明确反对把验收测试外包给独立的 QA 团队——写代码的人不为绿灯负责,红灯就会变成「别人的问题」然后堆积。这与 Ch3「红了停线」是同一条纪律。测试人员的价值也随之转移:不再是手工重复点击,而是定义验收标准、做探索性测试,这两件事机器都干不了。
如果这一章只能记住一张图,就是这张。验收测试要分成三层(第四层是被测应用本身):
规矩在中间那道墙:①、② 两层只许出现业务与领域的词汇,「点击 id 为 btn-submit 的按钮」这类话只许出现在 ③ 里,而且只出现一次。收益可以量化:登录页从「用户名 + 密码」改成「手机号 + 验证码」,没分层则凡是需要先登录的测试全要改——通常是整套测试的 80% 以上;分层之后改动落在驱动层那一个 login() 方法上,上面几千条一个字都不用动。验收测试的维护成本,几乎完全由「界面细节被复制了多少份」决定。
本章第二个关键判断,很多人第一次读会觉得矛盾:验收测试必须测「整个已部署的系统」,但入口应当尽量选在界面之下——通过应用对外的公共 API(也就是界面在背后调用的那一层)驱动它。
理由是速度与脆性的乘积。走界面的一条测试通常要 3~15 秒(等渲染、等异步加载),同一件事从公共 API 驱动往往只要 100~300 毫秒——差一到两个数量级;更要命的是脆:一次前端框架升级、一次布局调整就能让一批测试集体变红,而业务其实一点没坏。
那界面本身谁来测?本章的态度是:界面里若含业务逻辑,第一件该做的事是把逻辑挤出界面,剩下的纯展示部分留少量界面测试盯着。这条建议在今天的前端时代反而更贴切——现代前端堆着大量逻辑,正解依然是让它可被独立测试,而不是把几千条端到端测试架在浏览器上。
录制回放工具的卖点很诱人:不用写代码,点一遍就有测试。本章明确反对把它当验收测试的主力,三条理由都很硬:
本章也留了一个诚实的例外:面对没有任何测试、又必须先改起来的遗留系统,用它临时织一张粗网兜底是合理的过渡——但那是脚手架,不是地基。
验收阶段在流水线里的形态本章也定得很死:
四个要点,每个都有理由:
关于时长,本章不给「十分钟」那样的硬数字,只给相对约束:验收阶段慢得起,但必须短到一天能跑很多轮。工程上的答案就是并行——按业务场景切片撒到多台机器上,总耗时取决于最慢的那一片而不是所有片之和。这条路能走多远,§6 里 LMAX 的公开数字给了一个上限参考。
单元测试可以假装世界不存在,验收测试不能——它跑在一个有数据库、有历史数据的真实系统上。状态是验收测试一切诡异失败的源头:跑第二遍时用户名已存在、上一条测试留下的订单污染了统计、两条测试并行时抢同一个账号。本章给了三种应对姿势,并明确了偏好:
user-7f3a91)。代价是要提供造数据的手段,收益是可以任意并行、任意顺序跑——而并行是解决「慢」的唯一出路,所以这两件事其实是同一件事。头号反模式是全组共用一份「大胖测试数据库」:起步很快,然后随时间腐坏——没人记得哪条数据是给谁用的,没人敢删,最后它自己变成要维护的遗产。测试数据的完整讨论在 Ch12。
验收测试跑在真实系统上,就必须面对真实系统的三件麻烦事:
sleep。固定等待要么白等(拖慢整套)要么不够(变成不稳定测试),两头不讨好。表 1 · 一条验收测试该从哪一层驱动系统
| 驱动入口 | 单条量级 | 能覆盖到 | 脆性 | 什么时候选 |
|---|---|---|---|---|
| 隔着界面点 | 3~15 秒 | 全链路,含前端逻辑与渲染 | 最高:布局、控件 id、时序一改就红 | 界面里确实含逻辑时,保留少量主干路径 |
| 界面之下的公共 API | 100~300 毫秒 | 除界面外的完整业务链路 | 低 | 本章推荐的主力入口 |
| 消息 / 事件接口 | 几十毫秒 ~ 秒 | 异步与事件驱动链路 | 中:必须处理等待与乱序 | 事件驱动系统的天然入口 |
| 直接调领域对象 | 毫秒 | 单块逻辑,不含集成 | 极低 | 这已是单元测试,属提交阶段(Ch7) |
表 2 · 验收标准怎么落成可执行的东西
| 做法 | 谁读得懂 | 维护成本 | 代价 / 风险 |
|---|---|---|---|
| 团队自建 DSL | 开发 + 业务方(需引导) | 低 | 要自己造并维护这层语言;起步慢,长期最省 |
| Given/When/Then 工具 (Cucumber、FitNesse) | 业务方可直接读 | 中 | 业务方若并不真的读,就多了一层没人看的胶水 |
| 直接用测试框架写脚本 (配一个驱动层) | 只有开发 | 中低 | 失去「可执行规格」的沟通价值,退化成回归网 |
| 录制回放 | 没人 | 极高 | 与界面细节死绑、无法先写、脚本不可读;只配做遗留系统的临时脚手架 |
表 3 · 验收测试的状态(测试数据)怎么处理
| 策略 | 做法 | 优点 | 代价 |
|---|---|---|---|
| 测试隔离 | 每条测试自造数据,用唯一键 | 可任意并行、任意顺序 | 需要造数据的手段与清理策略 |
| 自适应测试 | 断言相对变化而非绝对值 | 能在有存量数据的环境里跑 | 断言变弱,写法更绕 |
| 测试排序 | 固定顺序,后一条吃前一条的状态 | 写起来省事 | 无法并行;一条红引发全线红,本章不推荐 |
| 共用大胖测试库 | 全组共享一份预置数据 | 起步最快 | 互相污染、随时间腐坏、最后没人敢删 |
表 4 · 验收阶段太慢了怎么办
| 办法 | 效果 | 代价 / 前提 |
|---|---|---|
| 按场景分片并行到多机 | 近线性缩短,最有效的一招 | 要求测试隔离到位;要花机器钱(见 §6 的 LMAX 数字) |
| 把入口从界面挪到 API | 常见一到两个数量级的提速 | 放弃对界面本身的覆盖,需另留少量界面测试 |
| 桩掉慢而不稳的外部系统 | 去掉最长的那段等待 | 桩与真件会漂移,必须另有集成测试兜底 |
| 共享昂贵的一次性准备 | 省掉重复的环境启动 | 共享即耦合,容易悄悄退回顺序依赖 |
| 删掉重复覆盖的场景 | 常有惊人收益 | 要看数据,不能凭感觉删 |
| 改成只在夜里跑一次 | 看起来省事 | 反馈从分钟级退回天级,等于回到 Ch1 的旧世界 |
表 5 · 一项检查到底该放哪一关
| 要验证的东西 | 放哪 | 为什么 |
|---|---|---|
| 算价函数的边界条件 | 提交阶段(Ch7) | 纯逻辑,毫秒级就能验完,不必启动系统 |
| 下单 → 付款 → 发确认信 | 验收阶段 | 业务价值只有在整个系统串起来时才成立 |
| 环境是不是活的 | 部署测试(冒烟) | 排在验收测试之前,能把环境问题一眼分流出来 |
| 容量 / 性能 / 安全 | 独立的非功能阶段(Ch9) | 需要基线与专用环境,节奏也不同 |
| 界面排版与视觉 | 少量界面测试 + 人工 | 自动化性价比最低的一类 |
| 「这个功能好不好用」 | 人工探索性测试 | 机器答不了这个问题,这正是测试人员被解放出来该做的事 |
五张表背后是同一条判据:验收阶段的每一分钟都很贵,所以只买那些「必须让整个系统串起来才能证明」的东西。能在提交阶段用毫秒验完的,别放进来;机器答不了的,别硬塞给机器。
这一章的主张今天全都变成了默认实践,只是换了名字:驱动层长成了业界通用的 Page Object 模式;Given/When/Then 长成了 Cucumber / SpecFlow / Behave 一整个工具家族;Selenium 的 WebDriver 接口成了 W3C 标准,Playwright、Cypress 是同一条路上的后来者;「生产类环境 + 冒烟先行」则变成了云上流水线标配的预发环境与部署后健康检查。
但今天最值得讨论的,是它引发的那场争论:端到端验收测试到底该有多少?正反两方都留下了公开可核实的数字。
面试与架构评审里这是高频考点。被问到「你们的端到端测试怎么做」,能拿分的答法不是报工具名,而是四个事实:有没有独立的驱动层(界面一改要动多少条测试)?套件跑一轮多久、并行度多少?失败里环境问题占多大比例、怎么和业务缺陷分流?不稳定的那些是隔离限期修,还是无限重跑?
① 验收测试验的是业务价值有没有交付,不是代码逻辑对不对;它站在用户角度,对整个已部署的系统跑。
② 这一章名义上讲测试、实质上讲需求:验收标准必须在开工前由分析、测试、开发、客户一起定义,最大的收益发生在写测试代码之前。
③ 核心手艺是分层:验收标准层(业务语言)/测试实现层(领域动作)/应用驱动层(唯一懂界面与接口细节的一层)。界面一改只动第三层,上面几千条不动。
④ 入口尽量选在界面之下的公共 API:单条从数秒降到百毫秒级,且不再被布局改动绊倒;界面里若有逻辑,正解是把逻辑挤出界面。
⑤ 不要用录制回放当主力:它要求应用先做完、把界面细节焊进每条脚本、产出的东西没人读得懂;只配做遗留系统的临时脚手架。
⑥ 验收阶段的形态:同一份制品 → 用生产同款脚本部署到生产类环境 → 先跑冒烟 → 并行跑验收 → 绿了晋级;顺带把部署流程每天演练很多遍。
⑦ 这一阶段最常见的失败是环境与部署,不是业务缺陷——冒烟测试的价值就在于一眼把两者分流。
⑧ 状态是验收测试最脏的地方:首选测试隔离(每条自造数据、唯一键),因为它同时解锁了并行——而并行是解决「慢」的唯一出路。
⑨ 异步用带超时的轮询而不是固定 sleep;时间要可控;外部系统用桩,但要诚实记账并另有集成测试兜底。
⑩ 端到端该有多少存在真实争议:Google 建议 70/20/10 并量化了「越大越不稳定」,LMAX 却把 11,000 条端到端测试压进 20 分钟。分水岭是有没有 DSL 与驱动层——没有它们,这一章的做法就会变成它自己警告的那种灾难。