如何将 gmock SaveArgPointee 与派生 class 的 std::shared_ptr 一起使用

How to use gmock SaveArgPointee with with std::shared_ptr of derived class

我有一个 BaseMessage class,我从中派生了几个不同的 DerivedMessage 子 classes 并想像这样发送它们:

class BaseMessage {
public:
   virtual std::vector<uint8_t> data() const noexcept = 0;
   virtual ~BaseMessage() = default;
   [...]
}

class DerivedMessage : public BaseMessage {
public:
   [...]
   std::vector<uint8_t> data() const noexcept override { return m_data; }
private:
   std::vector<uint8_t> m_data;
}

// simplified 
class Tcp {
public
   virtual void sendMessage(std::shared_ptr<BaseMessage> msg) { write(msg->data());}
   [...]
};

class SomeClass {
public:
   SomeClass(Tcp& tcp) : m_tcp(tcp) {}
   void writeDataToRemote(std::shared_ptr<DerivedMessage> derived) const {
      m_tcp.sendMessage(derived);
private:
   Tcp m_tcp;
}
};

现在我想用 gtest 为 SomeClass 编写测试。

因此我模拟了TCP的功能class:

class MockTcp : public Tcp {
MOCK_METHOD(void, sendMessage, (std::shared_ptr<ralco::CommandMsg> msg), (override));
[...]
}

让我们假设到这里为止所有内容都已简化但有效。

所以在测试中,我想检查函数writeDataToRemote中给sendMessage的参数。 我在 Whosebug 上读到了 ::testing::SaveArg::testing::SaveArgPointee(不过文档中没有)。

TEST(SomeClassTest, writesMessageToSocket){
   MockTcp mockTcp;
   SomeClass sc(mockTcp);
   
   // >>>how to declare msgArg here?<<<
   EXPECT_CALL(mockTcp, sendMessage(_)).Times(1).WillOnce(::testing::SaveArgPointee<0>(msgArg));
   const auto derivedMsg = std::make_shared<DerivedMessage>();
   sc.writeDataToRemote(derivedMsg);
   // further inspection of msgArg follows
}

如代码注释中所写,我不知道如何声明 msgArg 变量,以便它可以分配给 sendMessage 的实际参数。使用 SaveArg 时,我想我会得到一个悬空指针并按照上面的方式进行操作时出现错误,因为无法复制分配消息。感谢任何提示。

在您的情况下,您实际上只想保存并检查所考虑的 shared_ptr,因此使用 SaveArg:

就足够了
    MockTcp mockTcp;
    SomeClass sc(mockTcp);
    std::shared_ptr<BaseMessage> bm;
    EXPECT_CALL(mockTcp, sendMessage(_)).Times(1).WillOnce(::testing::SaveArg<0>(&bm));  // it will effectively do bm = arg;
    const auto derivedMsg = std::make_shared<DerivedMessage>();
    sc.writeDataToRemote(derivedMsg);
    // verify that the argument that you captured is indeed pointing to the same message
    std::cout << derivedMsg.get() << std::endl;
    std::cout << bm.get() << std::endl;

SaveArgPointee的常见误解是它将arg指向的值分配给测试中的局部变量,在您的情况下这可能不是一个好主意,因为它会调用Message的复制构造函数.

或者我可以推荐使用 Invoke。它非常通用且易于使用。你可以例如像这样捕获所需的参数:

    MockTcp mockTcp;
    SomeClass sc(mockTcp);
    std::shared_ptr<BaseMessage> bm;
    EXPECT_CALL(mockTcp, sendMessage(_)).Times(1).WillOnce(::testing::Invoke([&bm](auto arg) { bm = arg; }));
    const auto derivedMsg2 = std::make_shared<DerivedMessage>();
    sc.writeDataToRemote(derivedMsg2);
    std::cout << derivedMsg2.get() << std::endl;
    std::cout << bm.get() << std::endl;

或使用指向 BaseMessage 的原始指针:

    BaseMessage* rawBm = nullptr;
    EXPECT_CALL(mockTcp, sendMessage(_)).Times(1).WillOnce(Invoke([&rawBm](auto arg) { rawBm = arg.get(); }));
    const auto derivedMsg2 = std::make_shared<DerivedMessage>();
    sc.writeDataToRemote(derivedMsg2);
    std::cout << derivedMsg2.get() << std::endl;
    std::cout << rawBm << std::endl;