std::unique_ptr是mock替换的起点,因其支持编译期多态注入:被测类需通过std::unique_ptr持有抽象接口依赖,构造时接受派生mock对象,从而实现零侵入替换。

为什么 std::unique_ptr 是 Mock 替换的起点
因为 C++ 没有运行时类型擦除或反射,Mock 对象必须在编译期就“能被原对象位置接受”。最稳妥的方式是让被测类通过指针(尤其是智能指针)持有依赖,而不是直接聚合或值持有。否则你连替换入口都没有。
常见错误现象:class Service { Database db_; }; —— 这种写法下,Database 是硬编码的值类型成员,测试时根本没法塞进 MockDatabase。
- 把依赖改成
std::unique_ptr<database></database>或std::shared_ptr<database></database>,并在构造函数中注入 - 依赖接口必须是抽象基类(含纯虚函数),且析构函数为
virtual - 避免用
std::auto_ptr(已弃用)或裸指针(生命周期难管理) - 如果依赖来自单例或全局工厂,先解耦——把获取逻辑提到构造参数里
用 gmock 写 Mock 类时,EXPECT_CALL 的作用域容易错在哪
EXPECT_CALL 不是声明,而是“对后续某次调用的预期断言”,它绑定在当前 Mock 对象实例上,且默认只匹配一次。很多人以为写了就“一直生效”,结果测试跑过但实际没覆盖行为。
使用场景:验证被测代码是否按约定调用了依赖的某个方法,比如是否查了用户、是否发了通知。
立即学习“C++免费学习笔记(深入)”;
- 每个
EXPECT_CALL(mock_obj, Method(...))默认只校验一次调用;多次调用需显式写.Times(3)或.Times(::testing::AtLeast(1)) - 如果被测逻辑可能不触发该调用(比如条件分支),要用
.Times(::testing::AnyNumber())或改用ON_CALL设默认返回 -
EXPECT_CALL必须在被测代码执行前设置,否则会报Uninteresting mock function call - 多个
EXPECT_CALL对同一方法的设置,后设的会覆盖先设的(除非用.WillOnce()链式指定不同行为)
测试中传入 std::unique_ptr<mockx></mockx> 却提示类型不匹配?
这是因为 std::unique_ptr<mockx></mockx> 和 std::unique_ptr<interfacex></interfacex> 是不同类型,不能隐式转换——哪怕 MockX 继承自 InterfaceX。
性能 / 兼容性影响:强制转型(如 static_cast)可行但丑陋;更自然的做法是让被测类只认接口指针类型,构造时接受任意派生类的智能指针。
- 被测类构造函数参数应为
std::unique_ptr<interfacex>&&</interfacex>,而非具体 Mock 类型 - 创建 Mock 实例后,用
std::make_unique<mockx>()</mockx>,再 move 给被测类,编译器会自动向上转型 - 别在测试里写
std::unique_ptr<mockx> ptr = std::make_unique<mockx>(); service = Service(std::move(ptr));</mockx></mockx>—— 这会编译失败;要写成service = Service(std::make_unique<mockx>());</mockx> - 如果接口有 const 成员函数,Mock 类对应方法也必须加
const,否则EXPECT_CALL匹配失败
不用第三方库,手写轻量 Mock 的底线写法
不是所有项目都能引入 gtest/gmock,尤其嵌入式或遗留系统。这时候得靠虚函数 + 成员变量控制行为,核心是“可观察”和“可配置”。
可给出简短示例:
class MockLogger : public ILogger {
public:
mutable bool log_called = false;
mutable std::string last_msg;
void log(const std::string& msg) const override {
log_called = true;
last_msg = msg;
}
};
- 用
mutable标记状态变量,允许 const 成员函数修改(适配 const 接口) - 避免在 Mock 中做复杂逻辑(比如文件写入、线程 sleep),否则测试变慢且不稳定
- 如果需要模拟异常,加一个
bool should_throw = false;,在方法里if (should_throw) throw ...; - 手写 Mock 不处理调用次数、参数匹配等高级功能,所以仅适用于逻辑简单、路径明确的单元测试
真正麻烦的从来不是写 Mock,而是让被测类愿意接受它——接口设计、依赖生命周期、const 正确性,这些地方漏掉一个,桩就插不进去。









