最小的靶子
——测试越小,红灯越值钱
前两篇把“测试”当成一个东西来谈。其实测试有尺寸,而且尺寸几乎决定一切:同样是一盏红灯,照在不同大小的靶子上,价值可以差出几个数量级。
三种尺寸
工程界按被测对象的大小,把测试分成三层。
单元测试测最小的东西:一个函数、一个类。不碰数据库,不碰网络,不碰文件系统——所有外部依赖都换成替身,只考这一个零件本身。因为什么都不碰,它快到毫秒级,一个中等项目攒下几千条是常态。
集成测试测接缝:几个模块拼起来,验它们的交界处——A 的输出真的能被 B 消化吗?约定的格式两边理解一致吗?零件各自合格不等于装配无误,接缝有自己的失败方式。
端到端测试测整机:模拟真实用户,从入口一路走到出口——注册、下单、付款、收到邮件。它最接近真实,也最慢、最贵、最脆弱:一次要跑几分钟,环境稍有风吹草动就误报。
三层的健康比例是一座金字塔:底层大量单元,中层适量集成,顶层少数端到端。这个形状不是审美偏好,是下面这笔账算出来的。
为什么单元测试最值钱
第一,红灯的指向精度。 一条单元测试失败,指控是具体的:这个函数、这个输入、期望这个、得到那个——修理的人直奔现场。一条端到端测试失败,指控是“整个系统某处坏了”:可能是代码,可能是数据,可能是网络抖了一下,排查从头开始。失败的全部价值在于它指向哪里——靶子越小,指向越准;靶子大到整机,红灯就只是一声不知何处起火的警报。
第二,反馈的速度。 几千条单元测试几秒跑完,程序员每存一次盘就跑一遍,写错的代码活不过一分钟。端到端套件跑一遍半小时起步,于是一天只舍得跑几次,错误要几小时后才被发现——那时写错的人已经离开现场,记忆冷了,修复成本翻着倍涨。《可以被弹劾的结论》讲过考场前移:知道有检查在等,生产行为本身会被塑造——但这一条有个隐含前提,考试必须足够快。反馈慢到脱离现场,考场就前移不到生产的那一刻。
第三,红灯的信用。 端到端测试天然易碎:依赖时序、依赖环境、依赖第三方,常常无缘无故红一下、重跑又绿了。狼来了喊过几轮,团队就学会对红灯耸肩——而一套被耸肩对待的测试,比没有测试更糟,因为它还在提供虚假的安全感。单元测试是确定性的:同样的输入永远同样的输出,红了就是真有事。红灯的价值不在于亮,在于亮的时候没人敢不看。
第四,倒逼出好的结构。 想给一个函数写单元测试,就必须能把它单独拎出来——依赖要能替换,副作用要能隔离,边界要说得清。拎不出来的代码,往往不是不好测,是本来就纠缠不清。可测试性是设计质量的一张试纸。
所以金字塔的道理一句话说完:尽可能多的问题,要在尽可能小的靶子上暴露。 大靶子留给小靶子覆盖不了的东西——真实的接缝、真实的全流程——而不是用来补小靶子的懒。
倒过来的金字塔
现实中最常见的病态,是把金字塔倒过来:单元测试寥寥无几,质量保障几乎全压在少数几条端到端流程和人工点验上。工程界管这个形状叫冰淇淋筒。它的结局千篇一律:套件越跑越慢,失败越来越语焉不详,红灯从“必须处理”降级为“大概又是环境问题”,最后整套测试形同虚设——都在,没人信。
拿这个形状照一照法律工作的质检,照出来的正是一只冰淇淋筒。一份法律成品出门前的检查,传统上几乎全部压在顶层:资深律师把整份文书通读一遍。通读是端到端测试——最接近真实,也最慢、最贵、指向最模糊:“感觉哪里不对”是典型的整机红灯,从警报到定位,全靠通读者的功力和体力。而底层几乎是空的:没有人逐条核引文、逐个对要件——那被认为是通读顺便就做了的事。可是安检员一天看一万件行李的道理在这里同样成立:通读者的注意力撑不起零件级的核对,大靶子接不住小靶子的活。
按金字塔改造,先要回答:法律工作的“单元”是什么?是最小的可独立核验单位——一条引文(在不在卷宗里、法条现行有效吗)、一个要件(有没有对应的事实认定)、一处结论(语气配不配得上依据)。《弹劾的自动化》列过的那几类机械检查,全部落在这一层——这不是巧合:断言只写得出单元大小的。“这份意见书整体对不对”没法写成断言,“这条引文在不在”可以。粒度不是工程细节,粒度是自动化的前提。
中层也随之清楚:争点与争点的衔接、结论对前置结论的依赖、合同里定义条款与正文的一致、付款节点与交付义务的咬合——这些是接缝,零件各自无误不等于拼起来无误。顶层保留通读,但角色变了:不再负责捕捉一切,只负责小靶子和接缝都覆盖不了的东西——整体的判断、分寸、当讲不当讲。通读者从零件检验员解放出来,去做只有通读才做得了的事。
尾声
把大问题拆成小靶子,本来不是工程的发明。三段论干的就是这件事:把一团不透明的心证,拆成可以单独瞄准的前提——《可以被弹劾的结论》说它把整体的信心变成分段可攻击的对象。金字塔只是把这个古老的手艺推到底:拆到最小,弹劾才最有力。 一盏红灯的价值,从它照着的靶子有多小算起。