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

Posted

技术标签:

【中文标题】google mock - 我可以在同一个模拟对象上多次调用 EXPECT_CALL 吗?【英文标题】:google mock - can I call EXPECT_CALL multiple times on same mock object? 【发布时间】:2017-10-17 11:50:36 【问题描述】:

如果我在同一个 TEST_F 中的同一个模拟对象上调用 EXPECT_CALL 两次。 . .会发生什么?

是附加到模拟对象的期望还是第二次调用消除了第一次调用的影响?

我发现The After Clause 似乎暗示允许多次调用同一个模拟+ EXPECT_CALL。

【问题讨论】:

接受的答案是错误的,但有一些正确的地方。我已在其下发表评论以进行解释。这是一个更彻底和正确的答案:***.com/questions/44034633/… 更新:@luantkow 更新了他的答案,现在看起来不错。有关除此之外的更多见解,请参阅我在此处发布的超长答案。 【参考方案1】:

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

如果您想自己运行测试,但没有在您的系统上设置 googletest 或 googlemock,请here's a bare-bones project I created 让它在 Ubuntu 上快速启动并运行。去克隆它并自己玩。它也可以作为帮助您在 Mac 或 Windows 上运行的起点。

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

细微差别:

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

文档:

在阅读和研究以下文档时,将每个文档另存为(打印到)PDF,然后在 Windows、Mac 或 Linux 上免费使用 Foxit Reader 进行编辑、记笔记和随时突出显示或下划线 PDF。这样,您就可以记录需要记住的最重要的事情。请参阅我的*_GS_edit.pdf PDF here 和 here,了解我在学习 Google Test 和 Google Mock 时所做的笔记和标记 PDF。

Google 官方文档:

    gtest:都在这个文件夹中:https://github.com/google/googletest/tree/master/googletest/docs。要研究的关键文件可能是:
      入门 常见问题解答 示例(查看并仔细研究至少前 3 个示例的源代码) 高级
    gmock:都在这个文件夹中:https://github.com/google/googletest/tree/master/googlemock/docs。要研究的关键文件可能是:
      傻瓜 食谱 备忘单 - 这是所有文档中最好的一站式商店或“gmock 规则摘要”,但缺少一些甚至在(且仅在)“除了本文档之外,您还需要使用 for dummies 手册。 常见问题解答 傻瓜 回来并再次重新阅读此文档!在自己先将 gtest 和 gmock 原则付诸实践之后,第二次会更有意义。

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

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

答案:

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

A:首先,在这种情况下,您使用的是TEST() 宏还是TEST_F() 宏都没有区别。 TEST() 宏简单地扩展为一个从::testing::Test 类公开继承的类,TEST_F() 宏简单地扩展为一个从您的测试夹具类(TEST_F() 的第一个参数)继承的类,它必须从::testing::Test 类公开继承。

可以在同一个mock对象(mock类)上调用多个EXPECT_CALLs,从一般到具体,如下:

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

    每个模拟方法至少可以有一个EXPECT_CALL一个模拟类可以有许多模拟方法,因此每个方法可以有一个或多个EXPECT_CALLs 来配置预期与该方法的交互。因此,模拟类可以至少每个方法有一个EXPECT_CALL

    不应该有多个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));
    

    特别是,上述EXPECT_CALLs 每个都指定使用匹配签名调用myMockMethod() 必须恰好发生1 次。这是因为在这种情况下,cardinality rules 比隐含的.Times(1) 存在于每个EXPECT_CALLs 上,即使您没有看到它被写入。

    要指定您希望给定的EXPECT_CALL 匹配给定参数的任何 输入值,请使用::testing::_ 匹配器,如下所示:

    using ::testing::_;
    
    EXPECT_CALL(myMockClass, myMockMethod(_));
    

    在相同的模拟方法上不要有 具有相同匹配器签名的 EXPECT_CALLs 重复,但 多个 EXPECT_CALLs 和 在同一模拟方法上重叠/覆盖(但不重复)匹配器签名是可以的:如果您将多个 EXPECT_CALL 附加到相同的匹配值,则只有 最后一组会有任何效果。例如,参见 here、here 和 here。这意味着,如果您有两个或多个 EXPECT_CALLs 具有重复的匹配器签名(传递给模拟方法的相同参数),那么只有最后一个会得到任何调用。

    因此,您的测试将始终失败,除非在不寻常的情况下,除最后一个之外的所有 EXPECT_CALLs 都具有 .Times(0) 值,指定它们将永远被调用,因为确实如此是这样的:最后一个EXPECT_CALL 将匹配这些匹配器的所有调用,并且所有重复的EXPECT_CALLs 将有no 匹配调用!下面是一个测试示例,该测试会因这种行为而总是失败。这是@luantkow 关注in his answer here 的主要行为。

    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
    

    然而,这个奇怪的例外使测试有效,只需将所有重复的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
    

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

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

    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);
    

    各种谷歌文档说匹配的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 - how to say "function must be called ONCE with a certain parameter but ok to be called many times with different parameters"?。

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

这完全回答了这个问题。


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

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

同样,有关此主题的更多阅读,请阅读上文,并在此处查看我的其他答案:google mock - how to say "function must be called ONCE with a certain parameter but ok to be called many times with different parameters"?。


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

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

A:不,你不能这样做!尽管它可能似乎在测试中起作用,但根据 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();
    

那么,这里的有效解决方案是什么?好吧,如果您可以将此测试分成 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();        ?

以上每一项,?表示最多可使用一次,*表示可多次使用。

我们需要将.WillRepeatedly(action) 选项与action 一起使用,其中produces side effects 或calls a function, functor, or lambda 作为操作。

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

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

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

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

    ...所以事实证明我们不需要(我们甚至不允许)在这里指定.Times(::testing::AnyNumber())。相反,gmock 会根据these cardinality rules 自动计算出来,因为我们使用的是.WillRepeatedly()

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

    如果WillOnce()WillRepeatedly() 都不EXPECT_CALL() 中,则推断的基数是Times(1)。 如果有 nWillOnce()没有 WillRepeatedly(),其中n >= 1,则基数为@987654445 @。 如果有 nWillOnce()一个 WillRepeatedly(),其中 n >= 0,则基数为 @987654448 @。

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

    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);
        
    
    

    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 zero BEFORE you start the test!
        for (int i = 0; i < 10; i++)
        
            myOtherFunc();
        
        EXPECT_EQ(callCounter, 0);
    
    

    [最佳技术] 将 InvokeWithoutArgs(f)local 计数器变量和简单的 lambda 函数一起使用! 这很有效,并且比第一种方法,同时避免第二种方法的全局变量和附加的全局函数。这肯定是我最喜欢的处理方式,而且效果非常好。

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

    using ::testing::_;
    using ::testing::InvokeWithoutArgs;
    
    TEST_F(MyTestFixture, MyCustomTest)
    
        uint32_t callCounter;
    
        EXPECT_CALL(MyMockClass, myMockMethod(_, _))
            // Use a lambda function to set gmock to increment `callCounter` every 
            // time `myMockMethod()` is called with *any* input parameters!
            .WillRepeatedly(InvokeWithoutArgs([&callCounter]() callCounter++; ));
    
        // 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 zero BEFORE you start the test!
        for (int i = 0; i < 10; i++)
        
            myOtherFunc();
        
        EXPECT_EQ(callCounter, 0);
    
    

如果您认为应该将整个答案作为独立文件添加到 Gmock 文档中(我建议我们这样做),请单击下面的 github 问题链接并点赞。

练习使用 gtest/gmock:

    使用此项目练习编写和测试您自己的 google 测试和 google mocks。这也是如何使用 Google 的 Bazel build system:https://github.com/ElectricRCAircraftGuy/eRCaGuy_gtest_practice 启动和运行新项目的一个很好的演示。

相关:

    GitHub 问题我打开请求将此答案作为独立文档添加到其官方文档中。如果您同意,请到这里为这个问题点赞:https://github.com/google/googletest/issues/2775 google mock - how to say "function must be called ONCE with a certain parameter but ok to be called many times with different parameters"? Google Mock: multiple expectations on same function with different parameters google mock - how to say "function must be called ONCE with a certain parameter but ok to be called many times with different parameters"? Interleaving EXPECT_CALL()s and calls to the mock functions

【讨论】:

那些“待办事项”的意思是你还没写完答案? 是的,被孩子们打断了。稍后会回来讨论 - 希望今晚。 @FabiosaysReinstateMonica,完成!我刚刚在我的答案底部添加了 3 个大示例,并满足了所有 todos。如果您发现此答案有用,我将不胜感激。谢谢。这对我来说是一大堆学习。 当然,这是一个很棒的答案!您还向我展示了我一直在以错误的方式使用多个 EXPECT_CALL。我不确定这应该是一个独立的介绍,但它绝对应该集成到官方文档中。我创建了一个 GitHub 帐户只是为了给它投票。非常感谢! 这个问题回答了我们的问题【参考方案2】:

是的,您可以在同一个模拟对象上多次调用EXPECT_CALL。只要您确保在实际使用模拟方法之前调用了所有EXPECT_CALL。否则,您的测试将依赖于未定义的行为。来自ForDummies:

重要提示:gMock 要求在调用模拟函数之前设置期望值,否则行为未定义。特别是,您不能将 EXPECT_CALL() 和对模拟函数的调用交错。

如何处理多个调用?文档非常简单。来自ForDummies:

默认情况下,当调用模拟方法时,Google Mock 会搜索 期望以相反的顺序定义它们,并在 找到与参数匹配的主动期望(你可以认为 将其称为“新规则覆盖旧规则。”)。

让我们通过查看一些示例来考虑这对 gMock 用户意味着什么。我假设我们有一个带有以下标题的文件:

#include <gmock/gmock.h>

using namespace ::testing;

struct SomeMock

    MOCK_CONST_METHOD1(foo, void(int));
;

通过多次调用EXPECT_CALL的测试的最简单示例:

TEST(Examples, DifferentArgumentsGoingToBeOk)

    SomeMock mock;

    EXPECT_CALL(mock, foo(4)).Times(1); // exp#1
    EXPECT_CALL(mock, foo(5)).Times(1); // exp#2

    mock.foo(4); // call#1
    mock.foo(5); // call#2

测试工作直观:

call#1exp#2 不匹配,所以 exp#1 被尝试并匹配。 call#2exp#2 匹配。

两个调用只匹配一次,因此它们被认为是满足的并且测试通过了。

当多个EXPECT_CALL 能够匹配调用时,棘手的部分就开始了。让我们考虑以下示例:

TEST(Examples, TheSameArgumentsGoingToFail) // Test fails!

    SomeMock mock;

    EXPECT_CALL(mock, foo(4)).Times(1); //exp#1
    EXPECT_CALL(mock, foo(4)).Times(1); //exp#2

    mock.foo(4); // call#1
    mock.foo(4); // call#2

call#1exp#2 匹配。 gMock 在第一次匹配预期时停止,它根本不会检查 exp#1call#2exp#2 匹配。同样exp#1 没有机会匹配。

因此测试失败,因为exp#2 匹配了两次而不是一次,并且exp#1 根本不匹配。测试输出中打印的所有内容:

/tmp/so/main.cpp:26: Failure // exp#2
Mock function called more times than expected - returning directly.
    Function call: foo(4)
         Expected: to be called once
           Actual: called twice - over-saturated and active
/tmp/so/main.cpp:25: Failure // exp#1
Actual function call count doesn't match EXPECT_CALL(mock, foo(4))...
         Expected: to be called once
           Actual: never called - unsatisfied and active

另外,重要的是,添加新的期望不会禁用或删除旧的期望。他们仍然能够通过您的测试!

TEST(Examples, NewExpectCallDoesNotEraseThePreviousOne) // Test fails!

    SomeMock mock;

    EXPECT_CALL(mock, foo(4)).Times(1); // exp#1
    EXPECT_CALL(mock, foo(4)).Times(2); // exp#2

    mock.foo(4); // call#1
    mock.foo(4); // call#2

call#1call#2 都与 exp#2 匹配。结果,exp#2 得到满足,但测试将失败,因为 exp#1 匹配的次数不够。

如果出于某种原因,您需要编写像TheSameArgumentsGoingToFail 这样的测试,您可以使用多种技术来防止exp#2 第二次匹配。请参考文档InSequence usage、RetiresOnSaturation:

TEST(Examples, InSequenceExample)

    SomeMock mock;

    Sequence seq;

    EXPECT_CALL(mock, foo(4)).Times(1).InSequence(seq); //exp#1
    EXPECT_CALL(mock, foo(4)).Times(1).InSequence(seq); //exp#2

    mock.foo(4); // call#1
    mock.foo(4); // call#2


TEST(Examples, InSequenceExampleSecondApproach)

    SomeMock mock;

    InSequence seq;

    EXPECT_CALL(mock, foo(4)).Times(1); //exp#1
    EXPECT_CALL(mock, foo(4)).Times(1); //exp#2

    mock.foo(4); // call#1
    mock.foo(4); // call#2


TEST(Examples, RetiresOnSaturationExample)

    SomeMock mock;

    EXPECT_CALL(mock, foo(4)).Times(1); //exp#1
    EXPECT_CALL(mock, foo(4)).Times(1).RetiresOnSaturation(); //exp#2

    mock.foo(4); // call#1
    mock.foo(4); // call#2


TEST(Examples, AfterExample)

    SomeMock mock;

    auto& exp1 = EXPECT_CALL(mock, foo(4)).Times(1); //exp#1
    EXPECT_CALL(mock, foo(4)).Times(1).After(exp1); //exp#2

    mock.foo(4); // call#1
    mock.foo(4); // call#2

【讨论】:

您应该将EXPECT_CALL(mock, foo(4)); 替换为EXPECT_CALL(mock, foo(4)).Times(1); 以明确表明此EXPECT_CALL 将模拟函数设置为预期调用一次。您正在让自然规则接管将.Times() 隐式设置为1。请参阅此处以“If Times() is omitted, the cardinality is assumed to be”开头的段落... 您说:在以下文件中,测试TheSameArgumentsGoingToFail 将失败,因为第二个期望将匹配两次,而第一个将根本不匹配。 我投了反对票这个答案是因为这错误地将您拥有EXPECT_CALL(mock, foo(4)); 两次作为失败的原因这一事实。这是不是正确的。然而,“第二个期望将匹配两次而第一个将根本不匹配”真的。真正的原因是,如果你不指定.Times() 数字,gmock 会使用我上面评论中的规则来假设它,在这种情况下它是 1。 @GabrielStaples:我是否正确理解您否决了我的答案,因为我没有在示例中明确使用Times,同时意识到假设 1(这是我的意图)是记录在案的行为?我已经编辑了我的答案,因为:它根本不会改变我的答案;这对你和explicit is better than implicit 来说似乎很重要。尽管如此,我仍然认为我的答案是吹毛求疵。 我已多次阅读您提到的部分,但它并没有帮助我理解我的答案到底有什么问题。显示错误的代码示例在这里可能非常有用。我非常有信心我可能会提供一个代码来证明您的理解不正确,请检查gist.github.com/lukant/376df8c49a3aba8179879fadcea77dea 中的“theTestWillFailWhenOnlyLastExpectancyWasSatisfied”关于“只有最后一组会有任何影响”。如果您认为您可以改进我的回答并且您确信您的理解是正确的,请随时这样做。 我已编辑您的答案并对其表示赞同。我错过了你的观点,所以我不得不做出比预期更多的改变。不过,我现在完全理解你的观点,因为你的观点是 100% 正确的。我仍然认为你错过了我的观点,但现在在你的回答中明确说明(假设你保持我的编辑不变)2. Because Times(1) expects one call but mock.foo(4) is called twice. 如果有人想玩这个,我在这里创建了这个项目来修补: github.com/ElectricRCAircraftGuy/eRCaGuy_gtest_practice。观察测试错误和输出确实帮助我弄清楚了。【参考方案3】:

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

SomeMock mock;

EXPECT_CALL(mock, foo(4))
    .WillOnce(Return(16))
    .WillOnce(Return(42))
    .WillOnce(Throw(MyException()));

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

这通常比使用多个EXPECT_CALLRetiresOnSaturation 或其他技术更容易理解。

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

【讨论】:

我不明白为什么你不需要在这里指定.Times(3)..因为默认是Times(1)。 也许是因为它可以让人认为调用会被完成3x3次.. :O

以上是关于google mock - 我可以在同一个模拟对象上多次调用 EXPECT_CALL 吗?的主要内容,如果未能解决你的问题,请参考以下文章

Mock实现模拟python的对象

具有装饰器的模拟功能。再次使用相同的装饰器来装饰 Mock 对象并使其保持为 Mock

超简单本地mock假数据测试,模拟后台数据返回必杀技

springboot2.0入门----mock模拟测试+单元测试

Mock 模拟测试简介及 Mockito 使用入门

模拟一个类:Mock() 还是 patch()?