SuperAPI AI 工具集

testing-anti-patterns

测试与质量

查看项目

更新于 10-16 06:10

技能介绍

Testing Anti-Patterns 是一份面向编写或修改测试、添加 mock、或想在 production code 中加入仅测试用方法时使用的实践指南,目标是防止测试 mock 行为、污染生产代码等问题。

核心原则

测试必须验证真实行为,而不是 mock 行为。Mock 是隔离的手段,不是被测对象。核心原则是:测试代码做了什么,而不是 mock 做了什么。严格遵循 TDD 可以避免这些反模式。

三条铁律

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

反模式一:测试 mock 行为

违规示例是断言 mock 元素存在,例如渲染 Page 后断言 sidebar-mock 的 test id 在文档中。问题在于:你验证的是 mock 能工作,而不是组件能工作;mock 存在时测试通过,不存在时失败;这对真实行为没有任何说明。修复方式是测试真实组件,或者干脆不 mock 它,改为断言真实的导航角色元素;如果必须为隔离而 mock sidebar,也不要对 mock 断言,而是测试 Page 在 sidebar 存在时的行为。门控函数:在对任何 mock 元素断言之前,先问“我是在测试真实组件行为,还是只测试 mock 是否存在?”如果是在测试 mock 是否存在,就停止,删除断言或取消 mock 该组件,改为测试真实行为。

反模式二:生产代码中的仅测试方法

违规示例是在生产类 Session 中添加 destroy() 方法,而它只在测试的 afterEach 中被调用。问题在于:生产类被仅测试代码污染;如果在生产中被误调用会很危险;违反 YAGNI 和关注点分离;混淆对象生命周期与实体生命周期。修复方式是把清理逻辑放到测试工具中,例如在 test-utils 中提供 cleanupSession 函数,Session 本身不包含 destroy()。门控函数:在向生产类添加任何方法之前,先问“这是否只被测试使用?”如果是,就停止,不要添加,放到测试工具里。再问“这个类是否拥有该资源的生命周期?”如果不是,就停止,这个方法放错了类。

反模式三:未理解就 mock

违规示例是 mock 了 ToolCatalog 的 discoverAndCacheTools,使其返回 undefined,结果阻止了测试所依赖的配置写入,导致重复服务器检测测试无法按预期抛出异常。问题在于:被 mock 的方法带有测试所依赖的副作用;为了“安全”而过度 mock 会破坏实际行为;测试可能因错误原因通过,或神秘失败。修复方式是在正确的层级 mock,例如只 mock 慢的服务器启动部分,保留测试需要的行为。门控函数:在 mock 任何方法之前先停止,不要急着 mock。依次问:真实方法有哪些副作用?这个测试是否依赖其中某些副作用?我是否完全理解这个测试需要什么?如果依赖副作用,就在更低层级 mock(真正的慢操作或外部操作),或使用保留必要行为的测试替身,而不是 mock 测试所依赖的高层方法。如果不确定测试依赖什么,先用真实实现运行测试,观察实际需要发生什么,再在正确层级添加最小 mock。红旗信号包括:“为了安全我还是 mock 一下”“这可能很慢,最好 mock 掉”“不理解依赖链就 mock”。

反模式四:不完整的 mock

违规示例是只 mock 自己认为需要的字段,例如 mockResponse 只包含 status 和 data,缺少下游代码使用的 metadata。问题在于:部分 mock 隐藏了结构假设;下游代码可能依赖你未包含的字段,导致静默失败;测试通过但集成失败;带来虚假信心。铁律是:mock 完整的数据结构,就像它在现实中存在的那样,而不只是当前测试用到的字段。修复方式是镜像真实 API 的完整性,包含所有真实 API 返回的字段。门控函数:在创建 mock 响应之前,检查真实 API 响应包含哪些字段。行动步骤:查看文档或示例中的实际 API 响应;包含系统下游可能消费的所有字段;验证 mock 与真实响应 schema 完全一致。关键点:如果创建 mock,就必须理解整个结构;当代码依赖被省略的字段时,部分 mock 会静默失败。如果不确定,就包含所有有文档的字段。

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

违规表现是:实现完成、没有写测试、却说“可以测试了”。问题在于:测试是实现的一部分,不是可选的后续工作;TDD 本可以及早发现;没有测试就不能声称完成。修复方式是遵循 TDD 循环:先写失败测试,再实现使其通过,然后重构,最后才声称完成。

当 mock 变得过于复杂

警告信号包括:mock 设置比测试逻辑还长;为了让测试通过而 mock 一切;mock 缺少真实组件拥有的方法;mock 变化时测试就坏。此时可以问:“这里真的需要使用 mock 吗?”考虑使用真实组件的集成测试,往往比复杂 mock 更简单。

TDD 如何防止这些反模式

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。