当我开始为Beaver -Auth写测试时 我已经做了很多正确的事情了 每个单元都经过了多轮周密的审查。
计数保护,散装活塞,刷新旋转,TOTP再播放防御——设计是坚实的,我知道它是坚实的,因为我对每一块都仔细地思考了.
之后我针对实际代码写出238个测试,并发现了8个真正的bug.
其中有的是在第一天就无声地破坏生产 这个帖子不是具体关于虫子的——是关于"我仔细地审查了这个"和"这是可以被运送的"之间的空隙,为什么这个空隙比我们大多数人推测的要大,即使审查是真正小心的. "保钓考"和"可通"是不同的主张.
这是我几乎走进的陷阱: 我建造了一个坚固的测试套房, 覆盖了核心认证流—— 注册,登录,核查—— 和每一个测试都通过。
感觉好了 但通过测试只能告诉你 代码做了测试预期。
如果这些测试是用与代码相同的精神模型书写的,那么它们会高兴地确认一个bug是正确的行为,因为代码和测试都同意同样的错误假设.
修复不是"写更多的测试".
这是针对真实的,综合的系统进行的测试,而不是我自己的逻辑的手工模型,也不是孤立地测试模块。
下方有几只虫子被浮出水面,只是因为一个测试锻炼出真正的依赖链而不是假设它有效.
Bug 1: TypeScript 让一个参数转换错误编译干净 这个最让我害怕的 Beaver-Auth通过接口发送背景工作( 如发送验证邮件) : 默认执行已经漂移到不同的签名上——完全没有参数: 每个呼叫者仍然用全部四个参数引用它,与界面的形状相匹配.
从位置上来说,这意味着对象——一个平面数据对象——降落在了插槽中,其中代码预期到一个函数.
真正的函数落在了槽中 。
真实的被悄悄地抛下.
用默认的调度器在每次部署中运行,并投出——按字面上每个调度,这是几乎每个人都会使用的零配置默认.
每一个核实邮件。
每个密码重置邮件 。
寂静而永.
报告零出错。
不是警告,没什么 为什么?
TypeScript检查方法参数默认是两相变相的——允许的,一个参数较少的函数可以满足一个期望更多的接口,特别是当一个不匹配的参数类型是(它结构上接受任何东西)时.
类型系统在这里有一个真实的洞口,任何关于写出谨慎的代码的东西都不会抓住它——只有运行实际的调度路径才能抓住它.
修补是一行的签名修正.
教训是:编译类型签名并不能证明在呼叫地点实际商定的形状。
我增加了一个回归测试,通过真正的调度器(而不是测试的双倍)来锻炼这个精确的路径,这样就不能再无声地倒退了.
Bug 2: 一个看起来完整但实际上无法被称作的特性有一个方法——取消一个刷新取出的家庭,杀死一个JWT会话对新的访问符进行薄荷的能力.
清洁,在隔离中检验良好.
除了:无所事事,在公共API的任何地方,都曾向来电者归还过一分.
登录返回——没有。
整合这一软件包的消费者无法获得登出功能所需的单一信息。
该特征得到充分落实,完全无法接触到。
这是孤立的单元测试 永远无法弥补的漏洞-- 你通过直接通过一个你已经知道, 就像破碎的消费流量 来构建测试。
只有我写了一个整合测试, 角色扮演一个真正的消费者:登入,然后尝试只用登入反应给你的东西登出。
这个测试是无法写成的,没有立刻击中缺口.
修补:线穿过类型