SuperAPI AI 工具集

testing anti-patterns

测试与质量

查看项目

更新于 10-15 08:05

技能介绍

用途

Testing Anti-Patterns 是一份面向开发者的测试实践指南,用于识别和纠正测试代码中的常见错误做法。它的核心主张是:测试必须验证真实行为,而不是 mock 行为;mock 是隔离手段,不是被测对象。文档强调,严格遵循 TDD(测试驱动开发)可以预防这些反模式。

三条铁律

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

主要反模式

反模式 1:测试 mock 行为

错误做法是断言 mock 元素存在,例如用 screen.getByTestId('sidebar-mock') 判断侧边栏是否渲染。这样验证的是 mock 是否工作,而不是组件是否工作;测试在 mock 存在时通过、不存在时失败,无法反映真实行为。修正方式是测试真实组件,或干脆不 mock 该组件,改用 screen.getByRole('navigation') 这类基于真实语义的断言。

门禁自检:在对任何 mock 元素断言之前,先问“我是在测试真实组件行为,还是只测试 mock 是否存在?”如果答案是后者,就删除断言或取消 mock。

反模式 2:生产代码中的测试专用方法

错误做法是在生产类中加入只在测试中调用的方法,例如给 Session 类添加 destroy(),仅在 afterEach 中使用。这会污染生产类、在生产环境被误调用时存在风险、违反 YAGNI 与关注点分离,并混淆对象生命周期与实体生命周期。修正方式是把清理逻辑放到测试工具函数中,例如在 test-utils/ 中提供 cleanupSession(session)。

门禁自检:在向生产类添加方法前,先问“这是否只被测试使用?”如果是,就不要添加,改放到测试工具中;再问“这个类是否拥有该资源的生命周期?”如果不是,说明方法放错了类。

反模式 3:未理解依赖就 mock

错误做法是 mock 掉测试所依赖的副作用。例如测试“检测重复服务器”时 mock 了 ToolCatalog 的 discoverAndCacheTools,导致配置写入被阻止,第二次 addServer 本应抛错却没有抛错。原因是被 mock 的方法带有测试所依赖的副作用,过度 mock 反而破坏了真实行为。修正方式是在正确的层级 mock,例如只 mock 慢速的 MCPServerManager,保留测试所需的配置写入行为。

门禁自检:在 mock 任何方法前先停下来,依次问:真实方法有哪些副作用?本测试是否依赖其中某些副作用?我是否完全理解这个测试需要什么?如果依赖副作用,就在更低层级 mock(真正的慢速或外部操作),或使用能保留必要行为的测试替身,而不是 mock 测试所依赖的高层方法。如果不确定测试依赖什么,先用真实实现运行测试,观察实际需要发生什么,再在正确层级加入最小 mock。

红旗信号包括:“为了保险起见先 mock 一下”“这可能很慢,最好 mock 掉”“不理解依赖链就 mock”。

反模式 4:不完整的 mock

错误做法是只 mock 自己认为需要的字段,例如 mock 响应只包含 status 和 data,缺少下游代码会用到的 metadata。这会导致:部分 mock 隐藏了结构假设;下游代码可能依赖未包含的字段,造成静默失败;测试通过但集成失败;产生虚假信心。铁律是:mock 必须完整镜像现实中存在的数据结构,而不只是当前测试用到的字段。修正方式是补齐真实 API 返回的全部字段,例如加入 metadata: { requestId: 'req-789', timestamp: 1234567890 }。

门禁自检:创建 mock 响应前,先确认真实 API 响应包含哪些字段;查阅文档或示例中的实际响应;包含系统下游可能消费的所有字段;验证 mock 与真实响应结构完全一致。如果不确定,就包含所有有文档记录的字段。

反模式 5:把集成测试当作事后补充

错误做法是“实现完成、没有测试、声称可以测试了”。测试是实现的一部分,不是可选的后续步骤;TDD 本应在此前发现问题;没有测试就不能声称完成。修正方式是遵循 TDD 循环:先写失败测试、实现使其通过、重构,然后才声称完成。

mock 过于复杂的预警信号

  • mock 的设置代码比测试逻辑还长
  • 为了让测试通过而 mock 一切
  • mock 缺少真实组件拥有的方法
  • mock 一改测试就坏

此时应考虑:使用真实组件的集成测试往往比复杂 mock 更简单。

TDD 如何预防这些反模式

  • 先写测试:迫使你思考究竟在测试什么
  • 看它失败:确认测试测的是真实行为而非 mock
  • 最小实现:避免测试专用方法混入
  • 真实依赖:在 mock 之前先看清测试真正需要什么

如果测试的是 mock 行为,说明已经违反了 TDD——你在没有先让测试对真实代码失败的情况下就加入了 mock。

快速对照表

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

红旗清单

  • 断言检查 *-mock 测试 ID
  • 只在测试文件中被调用的方法
  • mock 设置占测试的 50% 以上
  • 移除 mock 后测试就失败
  • 无法解释为什么需要 mock
  • “为了保险起见”而 mock

结论

mock 是隔离工具,不是被测对象。如果 TDD 揭示出你在测试 mock 行为,说明方向已经错了。修正方式是测试真实行为,或者重新质疑为什么要 mock。

限制

本文档为测试实践指南,未提供具体安装命令、平台支持范围、收费或价格信息,这些内容暂未提供。