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

Dependency injection with unique_ptr to mock

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

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_;
};

目前一切顺利,效果很好。但是,当我想对代码进行单元测试时遇到问题:

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();
}

测试失败,因为 mock 对象被转移到 Foo,并且在这样的 mock 上设置期望失败。

DI的可能选项:

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

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;
};

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

实际上我不会在生产环境中推荐这种方法,但是 别名构造函数 shared_ptr 代表了对你的情况来说可能是一个肮脏但可行的解决方案。
一个最小的工作示例(不使用 gtest,抱歉,我来自移动应用程序,无法直接测试):

#include<memory>
#include<iostream>
#include<utility>

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

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

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

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

int main() {
    std::unique_ptr<Bar> bar = std::make_unique<Bar>();
    std::shared_ptr<Bar> shared{std::shared_ptr<Bar>{}, bar.get()};
    Foo foo{std::move(bar)};
    shared->DoSth();
    foo.DoIt();
}

我猜你的测试会变成这样:

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

struct FooTest : public testing::Test {
    FooTest() {
        std::unique_ptr<BarMock> bar = std::make_unique<BarMock>();
        barMock = std::shared_ptr<BarMock>{std::shared_ptr<BarMock>{}, bar.get()};
        out = std::make_unique<Foo>{std::move(bar)};
    }

    std::shared_ptr<BarMock> barMock;
    std::unique_ptr<Foo> out;
};

TEST_F(FooTest, shouldDoItWhenDoSth) {
    EXPECT_CALL(*barMock, DoSth());
    out->DoIt();
}

aliasing constructor 有什么作用?

template< class Y > 
shared_ptr( const shared_ptr<Y>& r, element_type *ptr );

The aliasing constructor: constructs a shared_ptr which shares ownership information with r, but holds an unrelated and unmanaged pointer ptr. Even if this shared_ptr is the last of the group to go out of scope, it will call the destructor for the object originally managed by r. However, calling get() on this will always return a copy of ptr. It is the responsibility of the programmer to make sure that this ptr remains valid as long as this shared_ptr exists, such as in the typical use cases where ptr is a member of the object managed by r or is an alias (e.g., downcast) of r.get()

毕竟,我最终到处都使用这种方法:

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 配合使用效果很好。

您可以在将模拟对象传递给构造函数之前保留对模拟对象的引用。我认为由于成员初始化顺序,它使代码有点脆弱,但它在语义上更清楚它的含义。 BarMock 的所有权仍然完全属于 Foo,引用句柄由 FooTest 保留(类似于 this answer)。

与你的答案基本相同,但使用引用而不是原始指针

class FooTest : public ::testing::Test
{
    protected:
        FooTest() :
            bar_mock_ptr(std::make_unique<BarMock>()),
            bar_mock(*bar_mock_ptr),
            foo(std::move(bar_mock_ptr))
        {}
    private:
        // This must be declared before bar_mock due to how member initialization is ordered
        std::unique_ptr<BarMock> bar_mock_ptr; // moved and should not be used anymore
    protected:
        BarMock& bar_mock;
        Foo foo; //ensure foo has the same lifetime as bar_mock
}