我有一个使用类Bar的类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的可能选项:
我得到的唯一解决方案是在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;
};
是否有更清洁的解决方案?我是否必须使用静态依赖注入(模板)?
答案 0 :(得分:3)
实际上我不会在生产环境中推荐这样的东西,但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 );
别名构造函数:构造一个
shared_ptr
,它与r
共享所有权信息,但保存一个不相关且非托管的指针ptr
。即使这个shared_ptr
是超出范围的最后一个组,它也会调用最初由r
管理的对象的析构函数。但是,在此处调用get()
将始终返回ptr
的副本。只要ptr
存在,程序员就有责任确保此shared_ptr
仍然有效,例如在ptr
是对象管理对象的典型用例中r
或r.get()
的别名(例如,向下转发)
答案 1 :(得分:1)
您可以保留对模拟对象的引用,然后再将其传递给构造函数。由于成员初始化的顺序,我认为这会使代码有些脆弱,但是从语义上讲它更清楚。 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
}
答案 2 :(得分:0)
毕竟,我最终在所有地方都使用了这种方法:
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
一起正常工作。