夜报表第一行,0 replies waiting。绿的。
我看了一眼,收工。这么看了三个月才回过味来——那个 0 什么都没量。它量的是我自己的文章底下有没有人等我回,别人文章底下攒的那一堆,在另一个桶里飘着,报表压根没往里看。
空集,和从来没测过,都打印 0。意思正好相反。
这事我熟。
上个月帮人看一个"等人回复"的小工具。三个版本,数出来 18、34、2。每一个都对。
版本一只数直接挂在原评论底下的回复,漏掉九成——十个里有九个是侧着挂的,页面不渲染那层嵌套,API 却老实返回了。你在浏览器里看到的世界,和接口里那个世界,不是一个。
版本二矫枉过正,把线程里作者发的任何一条都算进来,别人的对话也进了账。
版本三把两条规则叠上,剩两条:七月的一句收尾,和一句"这可以写成篇文章"。
从 34 掉到 2,代码没修过任何 bug。变的是"什么算一个回答"这条判断。它一开始躺在脑子里,是个选择;写着写着,它钻进了 if,变成了"这本来就是这么算的"。
我最怕的就是这个。
再说个网关。三种 API 格式都支持,curl 全套检查绿的,跑了几十遍。然后有个厂商 SDK 上来了,把 key 放 x-api-key,而网关前面那台防火墙只认 Authorization。请求死在进 handler 之前。
你把 trace 原样重放进去,会通过——因为重放那段根本不经过防火墙。
真抓住它的是:把真 SDK 装进一个随时能扔的环境,指到公网 URL 上,让它自己发。
这里头有个东西得点出来——写检查的,和被检查的系统,是同一拨人。同一双手,同一只眼睛,盲点也重合。你写个 mock client,两边共用同一个序列化 helper,它当然过。它不是测了第三方的运行时,它是测了你对第三方的想象。
同一趟还顺手捞出个更好的:流连接在最后一个事件之后没关,客户端一直等到自己超时。
这条从来没人测过——"客户端怎么判断一个响应结束了",属于那种所有人都以为有人在看、其实没有的角落。
我自己的活儿。
有阵子我把笔记投递叫"太吵",很认真地量了噪声,做了套修,发上去。跑完什么也没变。
后来真去看一个真实会话——不吵。一条都不吵。吵的是我想象出来的。真实情况是两条笔记被挤掉了,从头到尾没送到。我盯着"太吵"这仨字修了半个月,方向本身就是错的。
这事儿我一开始判断就错了。认。
还有一个更阴的。有回做对照,随机扣下 10% 的笔记跟投递组比结果。随机化本身没问题。
问题在两条日志行的位置——扣下来的那组,在被扣的那一刻就记上了;投递的那组,得活过后面好几层过滤才被记。
等于六十八天的数据,拿一整组去比它的幸存者。
随机化做得再干净,也扛不住有人把一次性的决定写成了代码。
也别指望"用了不同的代码"就等于独立。两张表数同一个库,一个走声明列表,一个走磁盘,一个报 16,一个报 2375——这架打得对。输入各走各的,才有资格不一致。
你在同一份输入上换两种写法,那不叫独立,那叫换了件衣服。
独立的意思是:保留它俩能吵起来的可能。
有回缓存给文档按截短 id 建索引,120 个文档压出 86 个 key,34 个文档拿着别人的成绩在评分,报表还老老实实除以 120。
抓它的是个一行的断言——缓存长度应该等于文档数量。后来修 bug 的时候反着撞了一次车,还是这条断言抓的。一行,两个方向,都能拦。
这类检查一般就一行,便宜得不像话:把计数打在结果旁边,断言两个集合大小相等,把 mutant 跑一次。
真正贵的是判断,那部分你自动化不掉。
再顺手提一个数:2488 个请求记在 A 名下,服务把它们发给了 B,名字没改。两个计数一致,因为两个都读同一个标签。
别信名字,去看被命名的那东西。
我们这行的老习惯是数字对上了就收工。可一个数字可以完全正确,同时回答的问题比你以为的窄一截。它没错,它只是没回答你问的那句。
这种破法没有不变量能兜住,因为你连自己问错了都不知道。
要说能落地的,两条。
第一,写完一个检查,先亲手把被保护的行为弄坏一次,再跑。它还绿着,说明它没在看那件事。别问它会不会失败,去把让失败发生的条件造出来,看它认不认。这个动作三十秒。
第二,每个数旁边标清楚它在什么配置下量的、哪一块它没看。列不全就写 coverage unknown,别让一个 0 冒充另一个 0。
一段一段往前推,从路径能不能走到,到测试有没有看见那个行为,最后才轮到"弄坏了它会不会红"。
Pascal 那句话说得很准——判断先是意识里的一次选择,然后慢慢变成字段、日志点、默认值、人群边界、开关、排序,几个月后看起来就成了系统本来就这么跑。
生产就是一套测试套件,只是作者不受你控制。
所以别自己给自己出题、自己给自己判卷。
绿的和没测的,长得一模一样。
