我有一个使用Foo类的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();
}

测试失败,因为模拟对象已从Foo进行了转移,并且对此类模拟设置期望值失败。

DI的可能选项:

shared_ptr的
  • :在这种情况下太多(Foo之间没有共享Bar对象)
    通过引用IBar来实现
  • :不是一个选项(Bar不在Foo外部存储,因此创建的Bar对象将被破坏,从而使Foo带有悬挂的引用)
  • :unique_ptr的
  • :无法通过所提供的方式进行测试
  • 通过值传递:是不可能的(将发生复制-与unique_ptr相同的问题)。

  • 我得到的唯一解决方案是在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;
    };
    

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

    最佳答案

    我实际上不建议在生产环境中使用某些东西,但是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 );
    

    关于c++ - 依赖注入(inject)与unique_ptr一起模拟,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/40508033/

    10-12 17:17
    查看更多