可测性强的代码需从接口设计、依赖管理、生命周期控制三方面入手,遵循Core Guidelines中IS.5、R.11、C.130等条款,通过纯接口+依赖注入解耦外部状态,禁用裸指针与隐式资源管理,确保函数行为可预测。

可测性强的代码,不是靠加一堆 mock 框架堆出来的,而是从接口设计、依赖管理、生命周期控制这三处下手——Core Guidelines 里 IS.5(接口应最小化依赖)、R.11(避免裸指针传递所有权)、C.130(用策略类或依赖注入解耦行为)这几条,就是最直接的落地依据。
用纯接口 + 依赖注入替代全局单例
测试时最常卡住的地方,是代码偷偷访问了 std::cout、std::time(nullptr)、Singleton::instance() 这类不可控外部状态。Core Guidelines 明确反对隐式依赖(见 I.22)。
- 把所有外部交互抽象成接口,比如
Logger、Clock、HttpClient,只暴露纯虚函数 - 构造函数接收这些接口的引用或智能指针(推荐
std::shared_ptr),不 new 不 static - 测试时传入
MockLogger或StubClock,无需宏替换或链接期 hack
class PaymentService {
public:
explicit PaymentService(std::shared_ptr clock,
std::shared_ptr logger)
: clock_{std::move(clock)}, logger_{std::move(logger)} {}
bool process(const Order& order) {
logger_->info("Processing order {}", order.id);
if (clock_->now() > order.deadline) {
return false;
}
// ...
}
private:
std::shared_ptr clock_;
std::shared_ptr logger_;
};
避免裸指针和隐式资源管理
Core Guidelines 的 R.3 和 R.11 强调:裸指针只用于观察,所有权必须由 std::unique_ptr 或 std::shared_ptr 明确表达。否则单元测试中极易出现 double-free、use-after-free 或无法控制析构时机的问题。
- 类成员中禁用
SomeClass*,改用std::unique_ptr(独占)或std::shared_ptr(共享) - 函数返回资源时,不用
new SomeClass,而用std::make_unique() - 测试中可安全地重置
std::unique_ptr,或用std::weak_ptr观察生命周期
让函数可预测:输入确定 → 输出确定
Core Guidelines 的 F.24 明确要求:无副作用的函数应声明为 const;有副作用的函数必须清晰表达其影响。否则测试断言会变成“猜行为”。
立即学习“C++免费学习笔记(深入)”;
- 纯计算函数(如
calculateTax(double amount, const TaxRate& rate))必须是noexcept且不读写成员变量 - 修改状态的函数(如
void addOrder(Order&& o))要显式命名,并在头文件注释中标明是否线程安全、是否会抛异常 - 避免在构造函数里做 I/O 或网络调用——这会让
TEST_F初始化直接失败,且无法 patch
测试桩(stub)与模拟(mock)的选择边界
GoogleTest 的 MOCK_METHOD 很方便,但 Core Guidelines 的 C.131 提醒:过度模拟说明接口粒度太细或职责不清。优先用 stub,仅当需要验证调用顺序/次数时才上 mock。
- Stub:返回固定值,比如
StubClock::now() { return std::chrono::system_clock::from_time_t(1717027200); } - Mock:只对真正关键的协作行为打桩,例如“支付失败时是否触发补偿回调”,而不是“是否调用了
logger->error” - 注意:mock 对象本身可能引入静态生命周期(如全局
MockHttpClient实例),违反I.3(接口不应依赖初始化顺序)
最难的其实不是写测试,而是让被测代码不抗拒测试——它体现在你第一次尝试给某个函数写单元测试时,是不是得先注释掉三行日志、临时替换两个单例、再把时间函数用宏包起来。如果答案是“是”,那不是测试框架不行,是代码没按 Core Guidelines 的约束去组织。











