google mock - 我可以在同一个模拟对象上多次调用EXPECT_CALL吗?

Bob*_*Bob 6 unit-testing googletest googlemock

如果我EXPECT_CALL在同一个模拟对象上调用两次TEST_F...怎么了?

期望是附加到模拟对象还是第二次调用会消除第一次调用的效果?

我发现After After Clause似乎暗示允许多次调用同一个mock + EXPECT_CALL.

Gab*_*les 17

以下所有代码均使用2019 年 10 月 3 日发布的Googletest/Googlemock v1.10.0进行了测试。

如果您想为自己运行测试但没有在您的系统上设置 googletest 或 googlemock,这里是我创建的一个准系统项目,用于在 Ubuntu 上快速启动和运行。去克隆它并自己玩它。它也可以作为帮助您在 Mac 或 Windows 上运行它的起点。

这是一个非常重要的问题,所以我觉得有必要对它进行破解。

细微差别:

首先让我说 Google Mock (gmock) 是有细微差别的。这意味着有很多微妙之处需要理解,这很困难。甚至文档也有点分散,您需要仔细阅读和研究才能真正掌握其中的一些甚至大部分细微差别,因为它们在重复每个文档中的某些要点方面做得不好. 所以,这里是所有的官方文档:如果你是为了工作这样做,告诉你的主管你将留出几天时间仔细阅读 gtest 和 gmock 文档并练习示例以牢牢掌握它.

文档:

在您阅读和学习以下文档时,将每个文档另存为(打印为)PDF,然后在 Windows、Mac 或 Linux 上免费使用Foxit Reader来编辑、记笔记以及在 PDF 中突出显示或下划线走。这样你就可以记下你需要记住的最重要的事情。请在此处此处查看我的*_GS_edit.pdfPDF ,了解我在学习 Google Test 和 Google Mock 时所做的笔记和标记 PDF 的示例。

官方谷歌文档:

  1. gtest:都在这个文件夹中:https://github.com/google/googletest/tree/master/googletest/docs。要研究的关键文件(按此顺序)可能是:
    1. 底漆
    2. 常问问题
    3. 样本(查看并仔细研究至少前 3 个样本的源代码)
    4. 先进的
  2. gmock:都在这个文件夹中:https://github.com/google/googletest/tree/master/googlemock/docs。要研究的关键文件(按此顺序)可能是:
    1. 假人用
    2. 烹饪书
    3. 备忘单- 这是所有文档中最好的一站式商店或“gmock 规则摘要”,但缺少一些甚至在(并且仅在)“傻瓜”手册中明确说明的内容,您将除了这个文档之外还需要。
    4. 常问问题
    5. 对于傻瓜<--是的,再次!在完成并尝试编写一堆测试和模拟之后,然后回来重新阅读本文档!在首先将 gtest 和 gmock 原则付诸实践之后,第二次会更有意义。

一般要记住的一些微妙规则:

  1. “请记住,测试顺序是未定义的,因此您的代码不能依赖于另一个测试之前或之后的测试”(https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#sharing -resources-between-tests-in-the-same-test-suite)。
  2. "重要说明: gMock 要求调用模拟函数之前设置期望值,否则行为未定义。特别是,您不能将EXPECT_CALL()s 和模拟函数的调用交错在一起" ( https://github.com/google/ googletest/blob/master/googlemock/docs/for_dummies.md#using-mocks-in-tests )

答案:

问题 1:“如果我EXPECT_CALL在同一个模拟对象上调用两次......TEST_F会发生什么?”

答:首先,在这种情况下,您使用的是TEST()宏还是TEST_F()宏没有区别。该TEST()宏只是展开成一个类从公开继承::testing::Test类,而TEST_F()宏只是扩展到一类继承你的测试夹具类(第一个参数TEST_F()),它必须公开从继承::testing::Test类。

EXPECT_CALL可以在同一个模拟对象(模拟类)上调用很多s,从一般到具体,如下:

EXPECT_CALL同一模拟对象上多个s的 3 条规则:
从最通用的 --> 最具体的(又名:“外部” -->“内部”范围)。

  1. 每个模拟方法至少可以有一个EXPECT_CALL一个模拟类可以有许多模拟方法,因此每个方法可能有一个或多个EXPECT_CALL配置与该方法的预期交互。因此,一个模拟类的每个方法至少可以有一个EXPECT_CALL
  2. 不应该有一个以上的 EXPECT_CALL每个匹配签名对单个模拟方法:(了解更多关于这3条以下)。每个模拟方法都有许多可以传入的不同参数,因此每个匹配器签名最多可以有一个EXPECT_CALL(可能的参数值或值的组合,在多个输入参数的情况下)。这意味着每个模拟方法可能有数千甚至数百万或数十亿有效且唯一的 EXPECT_CALLs 附加到它,每个都匹配一组不同的“匹配器”,或模拟方法的输入参数。例如,这是完全有效的:

    // Each `EXPECT_CALL()` in this example has a different and 
    // unique "matcher" signature, so every `EXPECT_CALL()` will
    // take effect for its matching parameter signature when
    // `myMockMethod()` is called.
    //                                    v--matchers
    EXPECT_CALL(myMockClass, myMockMethod(1));
    EXPECT_CALL(myMockClass, myMockMethod(2));
    EXPECT_CALL(myMockClass, myMockMethod(3));
    EXPECT_CALL(myMockClass, myMockMethod(4));
    EXPECT_CALL(myMockClass, myMockMethod(5));
    ...
    EXPECT_CALL(myMockClass, myMockMethod(1000));
    
    Run Code Online (Sandbox Code Playgroud)

    特别是,上面EXPECT_CALL的每个都指定myMockMethod()具有匹配签名的调用必须恰好发生1 次。那是因为在这种情况下,基数规则规定了.Times(1)每个EXPECT_CALLs上都存在一个隐式,即使您没有看到它被写入。

    要指定您希望给予EXPECT_CALL马赫的任何输入值给定参数,使用::testing::_匹配,就像这样:

    using ::testing::_;
    
    EXPECT_CALL(myMockClass, myMockMethod(_));
    
    Run Code Online (Sandbox Code Playgroud)
  3. 在同一个模拟方法上不要有具有相同匹配器签名的重复EXPECT_CALLs,但是在同一个模拟方法上具有重叠/覆盖(但不是重复)匹配器签名的多个 EXPECT_CALLs是可以的:如果您将多个附加EXPECT_CALL到相同的匹配值,只有最后一组会起作用。例如,请参见此处此处此处。这意味着如果您有两个或多个EXPECT_CALL具有重复匹配器签名的 s(传递给模拟方法的参数相同),那么只有最后一个会收到任何调用。

    因此,您的测试将始终失败,除非在异常情况下,EXPECT_CALL除了最后一个之外的所有s 都有一个.Times(0)值,指定它们永远不会被调用,事实确实如此:最后一个EXPECT_CALL将匹配这些匹配器的所有调用,并且EXPECT_CALL它上面的所有重复s 将没有匹配的调用!下面是一个测试的例子,它总是由于这种行为而失败。这是@luantkow在他的回答中关注的主要行为。

    using ::testing::_;
    
    // Notice they all have the same mock method parameter "matchers"
    // here, making only the last `EXPECT_CALL()` with this matcher
    // signature actually match and get called. Therefore, THIS TEST
    // WILL ***ALWAYS FAIL***, since EXPECT_CALL #1 expects to get 
    // called 1 time but is NEVER called, #2 through #1006, inclusive,
    // all expect to get called 2 times each but all of them are NEVER
    // called, etc.! Only #1007 is ever called, since it is last and
    // therefore always matches first.          
    //                                    v--matchers
    EXPECT_CALL(myMockClass, myMockMethod(_)).Times(1); // EXPECT_CALL #1
    EXPECT_CALL(myMockClass, myMockMethod(_)).Times(2); // EXPECT_CALL #2
    EXPECT_CALL(myMockClass, myMockMethod(_)).Times(2); // EXPECT_CALL #3
    EXPECT_CALL(myMockClass, myMockMethod(_)).Times(2); // EXPECT_CALL #4
    EXPECT_CALL(myMockClass, myMockMethod(_)).Times(2); // EXPECT_CALL #5
    EXPECT_CALL(myMockClass, myMockMethod(_)).Times(2); // EXPECT_CALL #6
    // ... duplicate the line just above 1000 more times here
    EXPECT_CALL(myMockClass, myMockMethod(_)).Times(3); // EXPECT_CALL #1007
    
    Run Code Online (Sandbox Code Playgroud)

    然而,这个奇怪的异常使测试有效,只需将除最后一个之外的所有重复EXPECT_CALLs设置为基本设置:.Times(0)

    using ::testing::_;
    
    // Notice they all have the same mock method parameter "matchers"
    // here, making only the last `EXPECT_CALL()` with this matcher
    // signature actually match and get called. However, since all previous
    // `EXCEPT_CALL` duplicates are set to `.Times(0)`, this test is valid
    // and can pass.          
    //                                    v--matchers
    EXPECT_CALL(myMockClass, myMockMethod(_)).Times(0); // EXPECT_CALL #1
    EXPECT_CALL(myMockClass, myMockMethod(_)).Times(0); // EXPECT_CALL #2
    EXPECT_CALL(myMockClass, myMockMethod(_)).Times(0); // EXPECT_CALL #3
    EXPECT_CALL(myMockClass, myMockMethod(_)).Times(0); // EXPECT_CALL #4
    EXPECT_CALL(myMockClass, myMockMethod(_)).Times(0); // EXPECT_CALL #5
    EXPECT_CALL(myMockClass, myMockMethod(_)).Times(0); // EXPECT_CALL #6
    // ... duplicate the line just above 1000 more times here
    EXPECT_CALL(myMockClass, myMockMethod(_)).Times(3); // EXPECT_CALL #1007
    
    Run Code Online (Sandbox Code Playgroud)

    在这里,只有EXPECT_CALL#1007(最后一个EXPECT_CALL)会匹配对 的调用myMockMethod(),并且Times(3)会生效。由于EXPECT_CALL此之上的所有重复s 永远不会匹配并被调用,因为它们永远不会到达,EXPECT_CALL对于给定匹配器的重复s 的测试对于.Times().Times(0)所有非最后位置重复EXPECT_CALLs之外的任何值总是会失败。

    这种使后期匹配器能够覆盖早期匹配器的效果是有意的,也是 Googlemock 设计的一部分,因为它允许您根据传递给模拟方法的值创建一种非常有用的预期调用层次结构,如下所示:

    using ::testing::_;
    
    // Most general matchers first (_ matches any input value)
    EXPECT_CALL(myMockClass, myMockMethod(_)).Times(1);
    // More specific matchers next, to override the more general matcher 
    // above if they match
    EXPECT_CALL(myMockClass, myMockMethod(7)).Times(2);
    EXPECT_CALL(myMockClass, myMockMethod(5)).Times(4);
    
    Run Code Online (Sandbox Code Playgroud)

    各种 google 文档都说匹配的EXPECT_CALLs 是按照相反的顺序下到上搜索的。因此,如果myMockMethod(8)被调用,它将根据EXPECT_CALL此方法的最后一个进行检查,该方法正在寻找myMockMethod(5). 这不匹配,所以它上升一个并检查myMockMethod(7). 这不匹配,所以它上升一个并检查myMockMethod(_). 这匹配!因此,它算作由Times(1)基数授权的一次调用。

    所以,你在上面定义的是:我们期望myMockMethod(5)被调用 4 次,myMockMethod(7)被调用 2 次,myMockMethod(anything_other_than_5_or_7)被调用 1 次。有关此主题的更多阅读,请参阅我的其他答案:google mock - 如何说“必须使用某个参数调用一次函数,但可以使用不同参数多次调用该函数”?.

关键摘要:关于“我可以EXPECT_CALL在同一个模拟对象上多次调用吗?”这个问题要记住的要点是:如果匹配器(指定要传递给的参数),您只能EXPECT_CALL在同一个模拟对象和方法上多次调用该模拟的方法)是不同的每个EXPECT_CALL。也就是说,当然,除非您设置.Times(0)了除最后一个以外的所有副本,否则EXPECT_CALL它们将变得毫无用处,因此请记住不要EXPECT_CALL使用相同匹配器的副本。

这完全回答了这个问题。


问题 2:“期望是附加到模拟对象上还是第二次调用消除了第一次调用的影响?”

上面的描述也回答了这个问题。本质上,EXPECT_CALL期望不会覆盖EXPECT_CALL它们之前的任何s的效果,除非匹配器(指定传递给模拟方法的值)相同或重叠,在这种情况下,只有最后一个 EXPECT_CALL才会被调用,因为它是总是在匹配序列中的其他人之前到达。因此,EXPECT_CALL在给定的模拟方法上不要有具有相同匹配器的重复s ,否则您可能会无意中迫使测试总是失败,因为上面的EXPECT_CALLs 永远不会被调用。这在上面的问题 1 中有详细讨论。

再次,有关此主题的更多阅读,请阅读上面,并在此处查看我的其他答案:google mock - 怎么说“必须使用某个参数调用一次函数,但可以使用不同的参数多次调用函数”?.


问题3:我可以调用EXPECT_CALL对mock方法设置一些期望,调用mock方法,然后EXPECT_CALL再次调用该方法以更改期望值,然后再次调用mock方法吗?

OP甚至没有明确提出这个问题,但我找到这个页面的唯一原因是因为我搜索了这个答案很多小时却找不到它。我的谷歌搜索是“ gmock multiple expect_call ”。因此,其他提出此问题的人也将落在此页面上,需要一个确定性的答案。

答:不,你不能这样做!尽管它在测试中似乎有效,但据 Google 称,它会产生未定义的行为。参见上面的一般规则#2!

"重要说明: gMock 要求调用模拟函数之前设置期望值,否则行为未定义。特别是,您不能将EXPECT_CALL()s 和模拟函数的调用交错在一起" ( https://github.com/google/ googletest/blob/master/googlemock/docs/for_dummies.md#using-mocks-in-tests )

因此,这是不允许的!

// EXAMPLE OF A BAD TEST THAT MAY SEEM TO WORK BUT IS RELYING ON *UNDEFINED* BEHAVIOR!
// The goal is to ensure that `myMockMethod()` is only called 2x the first time by 
// `myOtherFunc()`, 3x the second time, and 0x the last time.

// Google states: "**Important note:** gMock requires expectations to be set 
// **before** the mock functions are called, otherwise the behavior is **undefined**. 
// In particular, you mustn't interleave `EXPECT_CALL()`s and calls to the mock functions"
// (https://github.com/google/googletest/blob/master/googlemock/docs/for_dummies.md#using-mocks-in-tests)

using ::testing::_;

TEST_F(MyTestFixture, MyCustomTest) 
{
    // `myMockMethod()` should be called only 2x here by `myOtherFunc()`,
    // despite calling `myOtherFunc()` repeatedly
    EXPECT_CALL(MyMockClass, myMockMethod(_, _))
        .Times(2);
    for (int i = 0; i < 10; i++)
    {
        myOtherFunc();
    }

    // UNDEFINED BEHAVIOR BEGINS HERE: you can't interleave calls to `EXPECT_CALL` with 
    // calls to the mocked functions (in this case: `myMockMethod()`,
    // which is called by `myOtherFunc()`).

    // THEN `myMockMethod()` should be called 3x here by `myOtherFunc()`
    EXPECT_CALL(MyMockClass, myMockMethod(_, _))
        .Times(3);
    for (int i = 0; i < 10; i++)
    {
        myOtherFunc();
    }

    // LAST, `myMockMethod()` should be called 0x here by `myOtherFunc()`
    EXPECT_CALL(MyMockClass, myMockMethod(_, _))
        .Times(0);
    for (int i = 0; i < 10; i++)
    {
        myOtherFunc();
    }
}
Run Code Online (Sandbox Code Playgroud)

那么,这里有什么有效的解决方案?好吧,如果你能把这个测试分成 3 个不同的独立测试,那就去做吧!但是,如果这 3 个测试以无法将它们分开的方式相互关联呢?示例:您正在尝试测试一个限制功能,该功能将打印输出限制为每秒仅一次,例如,即使您尝试以每秒更频繁的方式打印。那么,在这种情况下,有一些变通方法。

首先,让我们回顾一下:根据Google Mock Cheat Sheet,以下是配置 的方法EXPECT_CALL()

EXPECT_CALL(mock-object, method (matchers)?)
     .With(multi-argument-matcher)  ?
     .Times(cardinality)            ?
     .InSequence(sequences)         *
     .After(expectations)           *
     .WillOnce(action)              *
     .WillRepeatedly(action)        ?
     .RetiresOnSaturation();        ?
Run Code Online (Sandbox Code Playgroud)

对于上面的每一项,?表示最多只能使用一次,而*表示可以使用任意次。

我们需要使用.WillRepeatedly(action)带有 an的选项,action会产生副作用调用函数、函子或 lambda作为动作。

这里有一些变通方法可以安全正确地执行上述具有未定义行为的测试。如果你想先看到最好的方法,直接跳到下面的#3:

  1. 使用Assign(&variable, value). 在这种特殊情况下,这有点 hacky,但它确实可以正常工作。对于您可能拥有的更简单的测试用例,这可能是满足您需求的完美方式。这是一个可行的解决方案:

    旁注:我在尝试运行 gmock 测试时得到的错误输出说:

    .Times()不能出现在.InSequence().WillOnce().WillRepeatedly()、 或.RetiresOnSaturation()之后

    ...所以事实证明我们不需要(甚至不允许.Times(::testing::AnyNumber())在此处指定。相反,gmock 会根据这些基数规则自动计算出来,因为我们正在使用.WillRepeatedly()

    如果您省略Times(),gMock 会为您推断基数。规则很容易记住:

    • 如果既不是 WillOnce()也不WillRepeatedly()EXPECT_CALL(),则推断的基数是Times(1)
    • 如果有n WillOnce()没有 WillRepeatedly(),其中n >= 1,则基数为Times(n)
    • 如果有n WillOnce()1 WillRepeatedly(),其中n >= 0,则基数为Times(AtLeast(n))

    这种技术实际上已经过测试并证明可以在真实代码上工作:

    using ::testing::_;
    using ::testing::Assign;
    
    TEST_F(MyTestFixture, MyCustomTest) 
    {
        bool myMockMethodWasCalled = false;
    
        EXPECT_CALL(MyMockClass, myMockMethod(_, _))
            // Set `myMockMethodWasCalled` to true every time `myMockMethod()` is called with
            // *any* input parameters!
            .WillRepeatedly(Assign(&myMockMethodWasCalled, true));
    
        // Do any necessary setup here for the 1st sub-test 
    
        // Test that `myMockMethod()` is called only 2x here by `myOtherFunc()`,
        // despite calling `myOtherFunc()` repeatedly
        for (int i = 0; i < 10; i++)
        {
            myOtherFunc();
    
            if (i < 2)
            {
                EXPECT_TRUE(myMockMethodWasCalled);
                myMockMethodWasCalled = false;        // reset
                EXPECT_FALSE(myMockMethodWasCalled);  // ensure reset works (sanity check)
            }
            else
            {
                EXPECT_FALSE(myMockMethodWasCalled);
            }
        }
    
        // Do any necessary setup here for the 2nd sub-test
    
        // Test that `myMockMethod()` is called only 3x here by `myOtherFunc()`,
        // despite calling `myOtherFunc()` repeatedly
        for (int i = 0; i < 10; i++)
        {
            myOtherFunc();
    
            if (i < 3)
            {
                EXPECT_TRUE(myMockMethodWasCalled);
                myMockMethodWasCalled = false;        // reset
                EXPECT_FALSE(myMockMethodWasCalled);  // ensure reset works (sanity check)
            }
            else
            {
                EXPECT_FALSE(myMockMethodWasCalled);
            }
        }
    
        // Do any necessary setup here for the 3rd sub-test
    
        // Test that `myMockMethod()` is called 0x here by `myOtherFunc()`,
        // despite calling `myOtherFunc()` repeatedly
        for (int i = 0; i < 10; i++)
        {
            myOtherFunc();
            EXPECT_FALSE(myMockMethodWasCalled);
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 使用InvokeWithoutArgs(f)与全局计数器变量和全局计数器功能。这很好用,而且比以前的方法更容易使用和更通用!请注意,如果需要,您也可以将此全局函数和变量迁移到您的测试夹具类中,这会稍微清理一下。

    这种技术实际上已经过测试并证明可以在真实代码上工作:

    using ::testing::_;
    using ::testing::InvokeWithoutArgs;
    
    static uint32_t callCounter = 0;
    static void incrementCallCounter()
    {
        callCounter++;
    }
    
    TEST_F(MyTestFixture, MyCustomTest)
    {
        EXPECT_CALL(MyMockClass, myMockMethod(_, _))
            // Set gmock to increment the global `callCounter` variable every time 
            // `myMockMethod()` is called with *any* input parameters!
            .WillRepeatedly(InvokeWithoutArgs(incrementCallCounter));
    
        // Do any necessary setup here for the 1st sub-test 
    
        // Test that `myMockMethod()` is called only 2x here by `myOtherFunc()`,
        // despite calling `myOtherFunc()` repeatedly
        callCounter = 0; // ensure this is zero BEFORE you start the test!
        for (int i = 0; i < 10; i++)
        {
            myOtherFunc();
        }
        EXPECT_EQ(callCounter, 2);
    
        // Do any necessary setup here for the 2nd sub-test 
    
        // Test that `myMockMethod()` is called only 3x here by `myOtherFunc()`,
        // despite calling `myOtherFunc()` repeatedly
        callCounter = 0; // ensure this is zero BEFORE you start the test!
        for (int i = 0; i < 10; i++)
        {
            myOtherFunc();
        }
        EXPECT_EQ(callCounter, 3);
    
        // Do any necessary setup here for the 1st sub-test 
    
        // Test that `myMockMethod()` is called 0x here by `myOtherFunc()`,
        // despite calling `myOtherFunc()` repeatedly
        callCounter = 0; // ensure this is

    • 绝对,这是一个很棒的答案!您还向我表明我一直在以错误的方式使用多个 EXPECT_CALL。我不确定这应该是一个独立的介绍,但它绝对应该集成到官方文档中。我创建了一个 GitHub 帐户只是为了投票。多谢! (2认同)

Mir*_*ral 9

另一种有用的技术(傻瓜指南中也有介绍)是只写一个,EXPECT_CALL但链接多组指示预期结果的操作。例如:

SomeMock mock;

EXPECT_CALL(mock, foo(4))
    .WillOnce(Return(16))
    .WillOnce(Return(42))
    .WillOnce(Throw(MyException()));
Run Code Online (Sandbox Code Playgroud)

这需要对具有相同参数的方法进行三次调用,并且前两次将返回指定的值,然后在第三次调用时抛出异常。

EXPECT_CALL这通常比使用多种和/或其他技术更容易理解RetiresOnSaturation

您也可以将其与 void 方法一起使用;您只需要使用DoDefault或更有趣的操作来代替Return


lua*_*kow 6

来自ForDummies:

默认情况下,当调用mock方法时,Google Mock将按照定义的相反顺序搜索期望值,并在找到与参数匹配的活动期望时停止(您可以将其视为"较新的规则覆盖旧的规则". ").

在以下文件中,测试TheSameArgumentsGoingToFail将失败,因为第二个期望将匹配两次,并且第一个期望将完全不匹配.请注意,测试'DifferentArgumentsGoingToBeOk`将通过.

#include <gmock/gmock.h>

using namespace ::testing;

struct SomeMock
{
    MOCK_CONST_METHOD1(foo, void(int));
};

TEST(Examples, TheSameArgumentsGoingToFail)
{
    SomeMock mock;

    EXPECT_CALL(mock, foo(4));
    EXPECT_CALL(mock, foo(4));

    mock.foo(4);
    mock.foo(4);
}

TEST(Examples, DifferentArgumentsGoingToBeOk)
{
    SomeMock mock;

    EXPECT_CALL(mock, foo(4));
    EXPECT_CALL(mock, foo(5));

    mock.foo(4);
    mock.foo(5);
}
Run Code Online (Sandbox Code Playgroud)

如果要使用相同的参数创建多个期望(InSequence用法,RetiresOnSaturation),则可以使用多种技术.它不是经常需要的.

  • @GabrielStaples:我是否正确理解,您否决了我的答案,因为我在示例中没有明确使用“Times”,同时意识到假设 1 (这是我的意图)是记录的行为?我已经编辑了我的答案,因为:它根本不会改变我的答案;这对你来说似乎很重要,“明确的比隐含的更好”。尽管如此,我还是认为我的答案是挑剔的,因此我会否决我的答案。 (4认同)