如何使用 shared_ptr 从外部提供模拟对象?

How to use shared_ptr to supply mock object from outside?

我想测试一个应用 Thud,它将使用资源 Foo。它不会拥有一个具体的对象,但会有一个指向抽象资源接口的指针(此处为IFoo)。在生产中,我将为它提供实际的资源实现 (FooImpl),而对于单元测试,我想发送一个指向模拟资源的指针。我应该怎么做?我已经尝试编写最少的代码来说明问题,

class IFoo
{
public:
    virtual bool Foo(bool) = 0;
};

class FooImpl : public IFoo
{
public:
    bool Foo(bool b) override
    {
        return b;
    }
};

class FooMock : public IFoo
{
public:
    MOCK_METHOD1(Foo, bool(bool));
};

class ThudTest : public ::testing::Test
{
protected:
    virtual void SetUp() {
        //foo.reset(&mock); //line#1
        foo = &mock; //line#2
    }

    FooMock mock;
    //std::shared_ptr<IFoo> foo; //line#3
    IFoo* foo; //line#4
};

class Thud
{
    //std::shared_ptr<IFoo> fooPtr; //line#5
    IFoo* fooPtr; //line#6
public:
    /*Thud(std::shared_ptr<IFoo> fooPtr_) : fooPtr{ fooPtr_ }
    {}*/ //line#7
    Thud(IFoo* fooPtr_) : fooPtr{ fooPtr_ }
    {} //line#8
    bool thud1(bool b)
    {
        return fooPtr->Foo(b);
    }
};

TEST_F(ThudTest, fooFalse)
{
    bool b = false;
    EXPECT_CALL(mock, Foo(b)).Times(1).WillOnce(Return(false));;

    Thud thud(foo);
    EXPECT_FALSE(thud.thud1(b));
}

TEST_F(ThudTest, fooTrue)
{
    bool b = true;
    EXPECT_CALL(mock, Foo(b)).Times(1).WillOnce(Return(true));;

    Thud thud(foo);
    EXPECT_TRUE(thud.thud1(b));
}

int main(int argc, char** argv) {
    // The following line must be executed to initialize Google Mock
    // (and Google Test) before running the tests.
    ::testing::InitGoogleMock(&argc, argv);
    return RUN_ALL_TESTS();
}

所以为了完成,我有一个重载的构造函数,它不进行争论但会执行以下操作,

Thud():fooPtr {std::make_shared<FooImpl>()}
{}

在生产中得到真正的实施。 但是现在如何使指针指向模拟对象。如您所见,我在这里使用的是 GMock 框架。如何实现?

如果我注释掉使用普通旧原始指针的第 2、4、6 和 8 行,并取消注释并使用第 1、3、5 和 7 行(使用此处有问题的 shared_ptr)相反,它在第一个测试用例后因堆损坏而崩溃。

它与这个原始指针完美配合。

你不能做 foo.reset(&mock),因为那时 mock 有两个所有者:它的自动存储持续时间,加上共享指针 foo。内存损坏 FTW。

您应该简单地动态分配 FooMock 并在创建正在测试的 Thud 实例时将其传入:

class ThudTest : public ::testing::Test
{
protected:
    virtual void SetUp() {
        foo = std::make_shared<FooMock>();
    }

    std::shared_ptr<FooMock> foo;
};

您甚至不再需要 mock 成员。请注意,我已将 foo 的类型更改为了解 FooMock,以便您可以访问该类型。如果你真的希望 foo 保持模拟无意识,请这样做:

class ThudTest : public ::testing::Test
{
protected:
    virtual void SetUp() {
        mock = std::make_shared<FooMock>();
        foo = mock;
    }

    std::shared_ptr<FooMock> mock;
    std::shared_ptr<IFoo> foo;
};

但是,这不是必需的,因为 std::shared_ptr<FooMock> 可以隐式转换为 std::shared_ptr<IFoo>

与:

class Thud
{
    std::shared_ptr<IFoo> fooPtr;
public:
    Thud(std::shared_ptr<IFoo> fooPtr_) : fooPtr{ fooPtr_ }
    {}
    bool thud1(bool b)
    {
        return fooPtr->Foo(b);
    }
};

您的测试可以变成(我删除了功能):

TEST(ThudTest, fooFalse)
{
    auto mock = std::make_shared<FooMock>()
    bool b = false;
    EXPECT_CALL(*mock, Foo(b)).Times(1).WillOnce(Return(false));;

    Thud thud(mock);
    EXPECT_FALSE(thud.thud1(b));
}

TEST(ThudTest, fooTrue)
{
    auto mock = std::make_shared<FooMock>()
    bool b = true;
    EXPECT_CALL(*mock, Foo(b)).Times(1).WillOnce(Return(true));;

    Thud thud(mock);
    EXPECT_TRUE(thud.thud1(b));
}

在我的解决方案中,需要使用 shared_ptr<IFoo> 将其添加到其他已测试 class 的集合中,我想出了使用引用 ;)。然后我也尝试了 NiceMock<IFoo>StrictMock<IFoo> 并且它起作用了。

using namespace testing;
using namespace std;

class ThudTest : public Test {
  public:
    shared_ptr<FooMock> fooPtr_ = make_shared<FooMock>();
    // shared_ptr<StrictMock<FooMock>> fooPtr_ = make_shared<StrictMock<FooMock>>();
    FooMock& mock = *fooPtr_;
    // StrictMock<FooMock>& mock= *fooPtr_;

    bool thud1(bool b) {
        return fooPtr_->Foo(b);
    }
};

并且可以在没有 * 的情况下编写测试:

TEST_F(ThudTest, fooFalse) {
    bool b = false;
    EXPECT_CALL(mock, Foo(b)).Times(1).WillOnce(Return(false));

    EXPECT_FALSE(thud1(b));
}

TEST_F(ThudTest, fooTrue) {
    bool b = true;
    EXPECT_CALL(mock, Foo(b)).Times(1).WillOnce(Return(true));

    EXPECT_TRUE(thud1(b));
}