SuperAPI AI 工具集

Testing Anti-Patterns

测试与质量

查看项目

更新于 10-15 14:00

技能介绍

Testing Anti-Patterns 是一份面向测试实践的指南,主题是识别并避免常见的测试反模式。其核心原则是:测试必须验证真实行为,而不是 mock 的行为;mock 只是隔离手段,不是被测对象本身。文档强调遵循严格的 TDD 可以防止这些反模式。

【三条铁律】

  1. 绝不测试 mock 行为。
  2. 绝不向生产类添加仅供测试使用的方法。
  3. 绝不在未理解依赖的情况下进行 mock。

【反模式一:测试 mock 行为】 违规示例是断言页面中存在 sidebar-mock 的 test id,这实际上是在验证 mock 是否工作,而非组件是否工作:mock 存在时测试通过、不存在时失败,无法反映真实行为。修正方式是测试真实组件,或干脆不 mock 该组件;若必须为隔离而 mock,则不要对 mock 本身做断言,而应测试页面在 sidebar 存在时的行为。门禁自查:在对任何 mock 元素断言之前,先问“我是在测试真实组件行为,还是只测试 mock 是否存在”,若是后者则删除断言或取消 mock。

【反模式二:生产代码中的测试专用方法】 违规示例是在 Session 类中定义 destroy() 方法,而该方法只在测试的 afterEach 中调用。问题在于:生产类被测试专用代码污染;若在生产中被误调用会很危险;违反 YAGNI 与关注点分离;混淆对象生命周期与实体生命周期。修正方式是把清理逻辑放到测试工具中,例如在 test-utils 中提供 cleanupSession,由测试调用。门禁自查:在向生产类添加任何方法前,先问“这是否只被测试使用”,若是则不要添加,改放到测试工具中;再问“这个类是否拥有该资源的生命周期”,若否则说明方法放错了类。

【反模式三:未理解依赖就 mock】 违规示例是 mock 掉 ToolCatalog 的 discoverAndCacheTools,但该方法的副作用(写入配置)正是测试所依赖的,导致重复服务器检测测试本应抛错却没有抛错。问题在于:被 mock 的方法带有测试所依赖的副作用;为“保险起见”过度 mock 会破坏真实行为;测试可能因错误原因通过或神秘失败。修正方式是在正确的层级 mock,例如只 mock 慢速的服务器启动部分,保留测试所需的行为。门禁自查:在 mock 任何方法前先停下,依次问“真实方法有哪些副作用”“本测试是否依赖其中某些副作用”“我是否完全理解本测试需要什么”;若依赖副作用,则应在更低层级(真正慢速或外部操作)mock,或使用保留必要行为的测试替身,而不是 mock 测试所依赖的高层方法;若不确定,先用真实实现运行测试,观察实际需要发生什么,再在正确层级添加最小 mock。预警信号包括“为保险起见先 mock”“这可能很慢,最好 mock 掉”“不理解依赖链就 mock”。

【反模式四:不完整的 mock】 违规示例是只 mock 自己认为需要的字段,遗漏下游代码会用到的 metadata,导致后续访问 response.metadata.requestId 时出错。问题在于:部分 mock 隐藏了结构性假设;下游代码可能依赖未包含的字段,造成静默失败;测试通过但集成失败;带来虚假信心。铁律是:mock 必须完整镜像现实中存在的数据结构,而不只是当前测试用到的字段。修正方式是让 mock 响应包含真实 API 返回的全部字段。门禁自查:创建 mock 响应前,检查真实 API 响应包含哪些字段,查阅文档或示例,包含系统下游可能消费的所有字段,并验证 mock 与真实响应 schema 完全一致;若不确定,则包含所有有文档记录的字段。

【反模式五:把集成测试当作事后补充】 违规示例是实现完成却没有写测试,就宣称“可以测试了”。问题在于:测试是实现的一部分,不是可选的后续步骤;TDD 本应及早发现;没有测试就不能声称完成。修正方式是遵循 TDD 循环:先写失败测试、实现使其通过、重构,然后才宣称完成。

【当 mock 变得过于复杂】 预警信号包括:mock 的搭建比测试逻辑还长;为了让测试通过而 mock 一切;mock 缺少真实组件具有的方法;mock 变化时测试就失败。此时应考虑:使用真实组件的集成测试往往比复杂 mock 更简单。

【TDD 如何防止这些反模式】 先写测试会迫使你思考究竟在测什么;看着测试失败能确认测试测的是真实行为而非 mock;最小实现可避免测试专用方法混入;使用真实依赖能让你在 mock 之前看清测试真正需要什么。如果测试的是 mock 行为,说明违反了 TDD——你是在没有先让测试对真实代码失败的情况下就加了 mock。

【快速对照】 对 mock 元素断言 → 测试真实组件或取消 mock;生产代码中的测试专用方法 → 移到测试工具;未理解就 mock → 先理解依赖,最小化 mock;不完整的 mock → 完整镜像真实 API;测试作为事后补充 → 采用 TDD,测试先行;mock 过于复杂 → 考虑集成测试。

【预警信号】 断言检查 *-mock 形式的 test id;只在测试文件中被调用的方法;mock 搭建占测试的 50% 以上;移除 mock 后测试就失败;无法解释为什么需要 mock;为“保险起见”而 mock。

【结论】 mock 是用于隔离的工具,不是被测对象。如果 TDD 揭示出你在测试 mock 行为,说明方向错了。修正方式是测试真实行为,或者质疑究竟为什么需要 mock。