使用 unique_ptr 进行依赖注入来模拟

我有一个使用类 Bar 的 Foo 类.Bar 只在 Foo 中使用,Foo 正在管理 Bar,因此我使用 unique_ptr(不是参考,因为我不需要 Foo 之外的 Bar):

I have a class Foo that uses class Bar. Bar is used only in Foo and Foo is managing Bar, therefore I use unique_ptr (not a reference, because I don't need Bar outside of Foo):

using namespace std;
struct IBar {
    virtual ~IBar() = default;  
    virtual void DoSth() = 0;
};

struct Bar : public IBar {
    void DoSth() override { cout <<"Bar is doing sth" << endl;};    
};

struct Foo {
  Foo(unique_ptr<IBar> bar) : bar_(std::move(bar)) {}

  void DoIt() {
    bar_->DoSth();
  }
private:
  unique_ptr<IBar> bar_;
};

到目前为止一切顺利,这工作正常.但是,当我想对代码进行单元测试时遇到了问题:

So far so good, this works fine. However, I have a problem when I want to unit test the code:

namespace {
struct BarMock : public IBar {
  MOCK_METHOD0(DoSth, void());
};
}

struct FooTest : public Test {
  FooTest() : barMock{ make_unique<BarMock>() }, out(std::move(barMock)) {}

  unique_ptr<BarMock> barMock;
  Foo out;
};

TEST_F(FooTest, shouldDoItWhenDoSth) {
  EXPECT_CALL(*barMock, DoSth());

  out.DoIt();
}

测试失败,因为模拟对象是为 Foo 传输的,并且对此类模拟设置期望失败.

The test fails because the mock object was transfered fo Foo, and setting an expectation on such mock fails.

DI 的可能选项:

  • by shared_ptr:在这种情况下太多了(Bar 对象不会在 Foo 之间共享任何其他东西)
  • 通过引用 IBar:不是一个选项(Bar 不存储在 Foo 之外,因此创建的 Bar 对象将被破坏,留下 Foo 的悬空引用)
  • by unique_ptr:无法以所提供的方式进行测试
  • 通过值传递:不可能(会发生复制 - 与 unique_ptr 相同的问题).

我得到的唯一解决方案是在 Foo 成为 BarMock 的唯一所有者之前存储指向 BarMock 的原始指针,即:

The only solution I got is to store raw pointer to BarMock before Foo become solely owner of BarMock, i.e.:

struct FooTest : public Test {
  FooTest() : barMock{new BarMock} {
    auto ptr = unique_ptr<BarMock>(barMock);
    out.reset(new Foo(std::move(ptr)));
  }

  BarMock* barMock;
  unique_ptr<Foo> out;
};

没有更干净的解决方案吗?我是否必须使用静态依赖注入(模板)?

Isn't there a cleaner solution? Do I have to use static dependency injection (templates)?

推荐答案

毕竟,我最终在任何地方都使用了这种方法:

After all, I ended up using this approach everywhere:

struct FooTest : public Test {
  FooTest() : barMock{new BarMock} {
    auto ptr = unique_ptr<BarMock>(barMock);
    out.reset(new Foo(std::move(ptr)));
  }

  BarMock* barMock;
  unique_ptr<Foo> out;
};

它与 gtest/gmock 一起工作得很好.

and it works fine with gtest/gmock.

相关文章