You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单例类底层指针类型替换的单元测试可行性探讨

问题描述

我有个单例类B,里面的RunTimer()是对外部定时器工具的封装。现在要给这个类做单元测试,想跳过定时器相关的逻辑,只测依赖其他参数的部分。Fake是我专门写的测试用类,带了测试需要的额外逻辑。

因为单例始终只有一个实例,想问下在单元测试里把std::unique_ptr<ITimer> _ptr替换成Fake的思路,在测试层面是否合理?

附上代码示例:

class ITimer
{
    public:
    virtual void foo() = 0;
    virtual ~Base() = default; // 注:此处应为~ITimer(),代码笔误
};

class Real : public ITimer
{
    public:
    void foo() override {}
};

// 单元测试用
class Fake : public ITimer
{
    public:
    void foo() override {}
};

class B
{
    std::unique_ptr<ITimer> _ptr;

    // 默认构造函数创建Real实例
    B() : B(std::make_unique<Real>())
    {

    }

    B(std::unique_ptr<ITimer> ptr) : _ptr(std::move(ptr))
    {

    }

    public:
    static B& get()
    {
        static B b;
        return b;
    }
 
    void RunTimer()
    {
       Timer::Run(); // 外部工具调用
    }
  
    void change(std::unique_ptr<Base> ptr) // 注:此处应为std::unique_ptr<ITimer>,代码笔误
    {
        _ptr = std::move(ptr);
    }
};

int main()
{
    B::get();       // 实际代码中使用Real
    
    // 测试场景(最终会放在TEST_F()中)
    auto mk = std::make_unique<Fake>();
    B::get().change(std::move(mk));
    // 对B::_ptr进行测试
}
解答

这个替换思路完全具有测试合理性,不过有几个细节要注意,还有优化空间:

1. 思路可行的核心原因

你这本质是用了依赖注入的变体——通过change方法替换单例内部的依赖实例,这是单例类做单元测试时常用的解耦手段:

  • 单例的全局唯一性确实容易导致测试用例互相污染,但只要能在测试前后重置状态,就能把各个用例隔离开;
  • Fake继承自ITimer接口,符合面向接口编程的原则,既能模拟真实ITimer的行为,还能添加测试需要的逻辑(比如记录调用次数、返回预设值),从而排除真实定时器的干扰,聚焦你要测试的核心逻辑。

2. 先修正代码笔误

你的代码里有两处明显问题,不改会导致编译失败:

  • ITimer的析构函数声明错误:应改为virtual ~ITimer() = default;,而非~Base();
  • B::change的参数类型错误:应改为std::unique_ptr<ITimer>,而非std::unique_ptr<Base>。

3. 优化测试稳定性的小技巧

单例的全局状态容易让测试用例互相影响,建议在测试框架中添加前后置处理逻辑:

  • 每个测试用例执行前,调用change注入Fake实例;
  • 测试完成后,再调用change恢复为Real实例,避免影响后续用例。

比如用Google Test的话可以这么写:

TEST_F(BTest, TestFoo) {
    // 测试前:替换为Fake
    auto fake_timer = std::make_unique<Fake>();
    B::get().change(std::move(fake_timer));

    // 执行测试逻辑
    // ...

    // 测试后:恢复为Real
    auto real_timer = std::make_unique<Real>();
    B::get().change(std::move(real_timer));
}

4. 长远架构优化方向

如果后续测试场景变复杂,这种“事后替换”的方式会显得不够优雅,可以考虑:

  • 给单例类添加测试专用初始化接口,允许在程序启动时就注入依赖,而非事后修改;
  • 或者重构单例为依赖注入容器管理的类,从根源上避免全局状态带来的测试问题。

内容的提问来源于stack exchange,提问作者xyf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 22:10:35