不能直接new真实服务对象做单元测试,因其依赖网络、数据库等外部资源,导致测试慢、不稳定、不可重复;应通过接口抽象(如c++纯虚类)解耦,并注入fake或mock实现以保障可控性与隔离性。

为什么不能直接 new 一个真实服务对象做单元测试
真实服务通常依赖网络、数据库、文件系统或第三方 API,导致测试变慢、不稳定、不可重复。比如 PaymentService 调用支付网关,每次跑测试都可能触发真实扣款或因网络超时失败。
- 测试执行时间从毫秒级变成秒级甚至分钟级
- CI 环境可能没权限访问生产/预发服务地址
- 多人并行运行测试时,共享状态(如数据库记录)互相污染
所以必须把外部依赖“摘掉”,换成可控的、行为确定的替代实现。
怎么设计可替换的接口抽象
C++ 没有内置的 interface 关键字(C++20 才有 concept),但可以用纯虚基类模拟接口。关键点是:让被测类只依赖抽象,不依赖具体实现。
- 抽象类必须只声明行为,不包含成员变量或实现
- 构造函数或 setter 必须接受该抽象类型的指针/引用(推荐智能指针,如
std::unique_ptr<ipaymentservice></ipaymentservice>) - 不要在抽象里暴露底层细节,比如
curl_easy_init或mysql_real_connect
示例:
class IPaymentService {
public:
virtual ~IPaymentService() = default;
virtual bool charge(const std::string& order_id, double amount) = 0;
};
被测类 OrderProcessor 就通过构造函数注入依赖:
class OrderProcessor {
std::unique_ptr<ipaymentservice> payment_;
public:
explicit OrderProcessor(std::unique_ptr<ipaymentservice> p) : payment_(std::move(p)) {}
bool process(const std::string& id) { return payment_->charge(id, 99.9); }
};
</ipaymentservice></ipaymentservice>
测试时怎么提供 Fake 或 Mock 实现
Fake 是有逻辑的轻量实现(比如用 std::map 模拟内存数据库),Mock 则侧重记录调用过程(比如验证是否调用了 charge 且参数正确)。
- Fake 更适合集成风格的测试,关注“结果是否符合预期”
- Mock 更适合行为验证,比如检查是否在失败后重试了三次
- 避免在 Fake 中引入新 bug:不要用复杂逻辑,也不要用全局状态(如 static map),否则多个测试用例会相互干扰
简单 Fake 示例:
class FakePaymentService : public IPaymentService {
std::map<:string bool> outcomes_;
public:
void setOutcome(const std::string& order_id, bool ok) { outcomes_[order_id] = ok; }
bool charge(const std::string& order_id, double) override {
auto it = outcomes_.find(order_id);
return (it != outcomes_.end()) ? it->second : true;
}
};
</:string>
测试中使用:
TEST(OrderProcessorTest, ShouldSucceedWhenPaymentReturnsTrue) {
auto fake = std::make_unique<fakepaymentservice>();
fake->setOutcome("ORD-123", true);
OrderProcessor proc(std::move(fake));
EXPECT_TRUE(proc.process("ORD-123"));
}
</fakepaymentservice>
容易忽略的生命周期和所有权问题
C++ 里依赖注入不是“传个指针就完事”。常见坑包括:
- 用裸指针传递依赖,但忘记明确谁负责 delete,导致悬空指针或内存泄漏
- 在测试中用
new创建对象,却没配对delete,CI 上 ASan 会报错 - 把 Fake 对象声明为局部变量,然后传
&fake给被测类,后续调用时对象已析构
稳妥做法统一用 std::unique_ptr 或 std::shared_ptr,并在构造时 move 进去。如果被测类需要长期持有,就用 unique_ptr;如果多个组件要共享同一实例(比如日志服务),才考虑 shared_ptr。
还有个隐形陷阱:某些服务内部缓存了单例句柄(比如 static CURL*),即使你替换了接口,底层仍走真实网络。这时得确保 Fake 实现完全绕过那些静态初始化逻辑——别继承、别调用、别链接对应库。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











