核心是依赖注入与接口抽象:业务代码面向统一接口编程,外部通过构造函数或参数注入具体实现,Mock 与真实服务共用同一契约,启动时按配置切换实现,源码无需修改。

核心在于把“依赖创建”从代码内部移出来,交给外部统一控制。只要接口一致,替换数据源就只是换一个实例的事,业务逻辑完全不用动。
用接口抽象统一行为契约
真实服务和 Mock 实现必须遵守同一套方法签名和调用约定。比如定义一个获取配置的接口:
- C++ 中用纯虚类:class IConfigProvider { public: virtual std::string Get(const std::string& key) = 0; };
- Go 中用 interface:type ConfigProvider interface { Get(key string) (string, error) }
- TypeScript 中用 interface:interface ConfigProvider { get(key: string): Promise
}
所有业务代码只面向这个接口编程,不关心背后是读本地 JSON、发 HTTP 请求,还是返回预设字符串。
构造函数或参数注入替代内部创建
把原来在类里直接 new 或调用全局函数的地方,改成由外部传入。
- 重构前:class PaymentService { private client = new HttpClient(); }
- 重构后:class PaymentService { constructor(private client: HttpClientInterface) {} }
- Hook 场景下:usePayment(client = fetch),测试时传 mockFetch,生产环境保持默认
这样切换数据源时,只需改入口处的一次传参或实例化,其余全部自动生效。
构建可插拔的 Mock 实现
Mock 不是简单 return 固定值,而是完整实现接口,并支持常见测试能力:
- 按输入返回不同结果:mock.Get("timeout") → throw new Error("network failed")
- 记录调用次数与参数:mock.calls.length === 2 && mock.calls[0].args[0] === "user_id"
- 支持延迟响应、并发模拟等边界场景
这类 Mock 类通常只存在于 test 目录,编译或打包时自动排除,不影响生产产物。
通过启动配置或环境变量控制注入源头
最终决定用哪个实现,靠的是最外层的组装逻辑,而不是业务代码里判断:
- CMake 中用 option(USE_MOCK OFF) 控制是否链接 Mock 模块
- Go 主程序根据 -mock 标志选择初始化 NewRealConfigProvider() 还是 NewMockConfigProvider()
- TypeScript 项目读取 import.meta.env.MODE === 'test' 加载对应 services 注册表
源码零修改,变的只是组装方式和注入点。

















