【问题标题】:Mock a method and capture one of its output parameters after the execution模拟方法并在执行后捕获其输出参数之一
【发布时间】:2021-06-21 03:52:20
【问题描述】:

我正在尝试模拟成员方法parseString,以便:

  1. 保持原来的行为,即返回原来的方法返回值作为原来的方法填充输出参数(std::vector<std::string>& iReqs);
  2. 我可以检查其输出参数之一 (std::vector<std::string>& iReqs)

可能有一种简单的方法可以做到这一点,但到目前为止我还没有找到一种干净的方法。 我最接近的是以下内容,使用 GMock:

  1. 使用 lambda 来模仿原始方法的行为
  2. 使用SaveArgs存储参数

这种方法的问题是SaveArgs 不返回任何值,并且它使测试在发布模式下编译崩溃。如果我反转两个DoAll 语句,SaveArgs 在方法实际填充它之前被调用,所以它没有意义。如果我添加步骤 3. 并稍微修改步骤 1.:

  1. 使用 lambda 来模仿原始方法行为存储原始返回值;
  2. 使用SaveArgs存储参数;
  3. 返回原来的返回值;

然后它似乎可以工作,除了我的测试中的一种情况,所以看起来可能有一些未定义的行为隐藏在这个解决方案.

代码sn-p:

class FooMock : public Foo
{

public:

    FooMock() : Foo()
    {
        // By default, all calls are delegated to the real object.
        // Moreover, we capture intermediary object for testing purposes
        // We need to save _originalReturn otherwise we will have undef behaviour
        ON_CALL(*this, parseString).WillByDefault(DoAll(
            ([this](const std::string& iDoc,
                    std::vector<std::string>& iReqs) {
                        _originalReturn = this->Foo::parseString(iDoc, iReqs);
                        return _originalReturn;
                    }),
            SaveArg<1>(&_requests),
            Return(_originalReturn)
        ));
    }

    MOCK_METHOD2(parseString, bool (const std::string& iDoc,
                                    std::vector<std::string>& iReqs));

    virtual ~FooMock() = default;

    std::vector<std::string> _requests;

private:
    bool _originalReturn = false;
};

class Foo
{
public:
    Foo() = default;

    virtual ~Foo() = default;

    bool execute( const std::string& iMessage ) { 
         // Calling parseString here
         return true; 
    }

protected:
    virtual bool parseString(const std::string& iMessage,
                             std::vector<std::string>& oBom){
         //Does something here
         return true;
    }        
};

在 GTest 中,我应该能够做到以下几点:

TEST_F( FooMockTest, basicRequest )
{
    std::string aString = "Something";

    FooMock uc;
    
    EXPECT_CALL(uc, parseString(_, _)).Times(1);

    EXPECT_TRUE(uc.execute(aString));

    // Test some property of the output parameter...
    ASSERT_EQ(1U, uc._requests.size());
   
}

同样,我无法找到一种方法来连接我希望模拟的成员方法的两种行为,即保留其原始行为并在其原始执行后保存 args。

我认为我最新的 sn-p 不安全,可能会隐藏一些不一致(因为我通过一个中间变量作为 Mocked 类成员字段)。

你能帮忙吗?谢谢!

【问题讨论】:

  • 我想帮忙,但由于 sn-p 不是minimal reproducible example,我不想花时间解决编译器错误。特别是 { 没有匹配的标记,Boo 和 b 未定义,缺少包含的标头和使用指令...
  • @S.M.感谢您的反馈意见。我更新了快照以使其具有连贯性,对不精确表示抱歉。顺便说一句,sn-p 应该编译(这不是编译问题,而是运行时问题)。这个问题并不完全清楚,我同意,我试图改进它。
  • 我认为你误解了mock对象和gmock的用法:FooMock可以用来代替Foo进行测试但不能自己测试,ON_CALL似乎被调用了测试用例。

标签: c++ unit-testing googletest gmock


【解决方案1】:

首先,我在这里看不到任何 UB。如果您想获得帮助,请创建单独的问题。

关于这个,我建议您正确使用 gMock 匹配器。基本上,你想要一些微不足道的东西,但你只是使用了错误的工具。在您的代码中,您在 EXPECT_CALL 中使用了占位符,而不是实际的匹配器:

EXPECT_CALL(uc, parseString(_, _)).Times(1);

当您不在乎调用期间使用了哪些参数时,必须使用这些占位符。但是您关心它们,甚至创建了专门的领域来比较它们。所以你只需要使用适当的匹配器,在这种情况下Container matchers 来验证在该调用期间使用了哪些项目。在您 sn-p 中,您正在验证项目的数量,可以使用 SizeIs matcher 完成:

EXPECT_CALL(uc, parseString(_, SizeIs(1))).Times(1);

还有很多其他非常复杂的匹配器可以帮助您在任何情况下匹配参数。例如,您要确保在调用parseString 时,第二个输入参数包含一个具有子字符串“MAD”的字符串。它看起来像这样:

EXPECT_CALL(uc, parseString(_, Contains(HasSubstr("MAD")))).Times(1);

所以我的问题描述的任务版本将如下所示:

class Foo {
public:
    virtual ~Foo() = default;

    bool execute(const std::string& iMessage)
    {
        std::vector<std::string> Bom;
        Bom.push_back("first_item");
        return parseString(iMessage, Bom);
    }

protected:
    virtual bool parseString(const std::string& iMessage,
        std::vector<std::string>& oBom)
    {
        oBom.push_back("extra_item");
        return true;
    }
};

class FooMock : public Foo {

public:
    FooMock()
    {
        ON_CALL(*this, parseString).WillByDefault([this](const std::string& iDoc, std::vector<std::string>& iReqs) {
            return Foo::parseString(iDoc, iReqs);
        });
    }
    MOCK_METHOD2(parseString, bool(const std::string& iDoc, std::vector<std::string>& iReqs));
};

TEST(FooMockTest, basicRequest)
{
    std::string aString = "Something";

    FooMock uc;

    EXPECT_CALL(uc, parseString(aString, SizeIs(1))).Times(1);
    EXPECT_TRUE(uc.execute(aString));
}

在这种情况下,我们将验证 parseString 将在调用 execute 方法期间被调用一次,它将接收传递给 execute 的相同字符串和带有一项的向量(在这种情况下向量包含“ first_item”元素)。

【讨论】:

  • 哦,我明白了!这是否意味着在调用结束时也应用了匹配器,我不知道,我以为它只是为了验证输入参数,而不是验证 I/O 参数。
  • 我认为我需要使用OnChanged() 指令顺便说一句,以表明我想匹配输出参数...
  • 它不起作用,因为它正在捕获参数之前执行og方法
  • 如果您想测试parseString 方法的结果 - 只需为该方法编写简单的测试即可。在这种情况下,没有理由使用模拟。我的建议是为了合理使用模拟系统。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-26
  • 2011-02-09
相关资源
最近更新 更多