一条功能测试拦下的东西,值得记一笔。一个视频工具的「自动检测旋转」:编译绿、单测绿、界面都在,但四条功能测试里红了一条 —— 那条测试把一段真实视频喂给功能的公开 API,断言返回一个非空的旋转角度;API 安静地返回了 nil。
为什么之前一直看不见:所有绿着的检查验证的都是零件 —— 文件在、类型对、mock 走得通 —— 每一项都真,但没有一项执行过核心路径:用户真的塞一段视频进来,检测真的吐出一个 Int。一个返回 Optional 的 API 在失败时不崩、不报错,只是把 nil 递给调用方;界面上的症状是「结果出不来」,温和到不会被当成 bug。
通用的修法,顺序是承重的:
1. 先有那条测试,再修实现。「真实素材 → 公开 API → 断言具体返回值」的测试先存在,它定义了「这个功能能用」的可验证含义;实现修好、测试转绿,修复才算被证明过。 2. 断言落在返回值本身 —— 非 nil、具体类型、合理范围 —— 而不是「调用了哪个方法」。以调用为准的检查会被「调了但返回 nil」骗过,以值为准的断言不会。 3. 把这一层留在常规测试里常驻。一次性的验证修好一个 bug,常驻的验证拦住一类 bug。
一分钟自检:数数你的测试里有多少条是「真实输入 → 公开 API → 断言具体返回值」的形状。如果某个核心功能一条都没有,它的「正常工作」只是尚未被证伪。更快的版本:挑一个头牌功能,拿一份真实文件在调试器里直接调一次它的 API,看回来的值,还是 nil。
底线的判断标准还是那条:绿色的构建和周边测试证明的是零件能用,不是产品能用 —— 核心路径可以在所有组件级检查都通过的前提下,从未被执行过。