gmock 设置默认操作 / ON_CALL vs. EXPECT_CALL

gmock setting default actions / ON_CALL vs. EXPECT_CALL

本文关键字:CALL vs EXPECT ON 设置 默认 操作 gmock      更新时间:2023-10-16

我不明白使用它来ON_CALL和EXPECT_CALL之间的区别 指定默认操作。

到目前为止,我注意到/了解到有两种方法可以调整模拟的默认动作:

ON_CALL(mock, methodX(_)).WillByDefault(Return(0x01));

EXPECT_CALL(mock, methodX(_)).WillRepeatedly(Return(0x01));

有人可以向我解释一下:

  • 两种方法的区别
  • 每个人的起起落落
  • 什么时候适合使用它们(什么样的设置...

这两种说法之间存在微妙但显着的差异。EXPECT_CALL对模拟呼叫设置期望。写作

EXPECT_CALL(mock, methodX(_)).WillRepeatedly(do_action);

告诉 gMock,methodX可以使用任何参数调用任意次数mock,当它被调用时,mock将执行do_action。另一方面

ON_CALL(mock, methodX(_)).WillByDefault(do_action);

告诉 gMock 每当在mock上调用methodX时,它都应该执行do_action。该功能在您必须在模拟上写下许多期望的情况下非常有用,并且大多数/所有期望都必须指定相同的操作 - 特别是如果它很复杂。您可以在ON_CALL中指定该操作,然后编写EXPECT_CALL而不显式指定该操作。例如,

ON_CALL(mock, Sign(Eq(0), _))
.WillByDefault(DoAll(SetArgPointee<1>("argument is zero"), Return(0)));
ON_CALL(mock, Sign(Gt(0), _))
.WillByDefault(DoAll(SetArgPointee<1>("argument is positive"), Return(1)));
ON_CALL(mock, Sign(Lt(0), _))
.WillByDefault(DoAll(SetArgPointee<1>("argument is negative"), Return(-1)));

现在,如果你必须编写很多EXPECT_CALL,你不必每次都mock指定行为:

EXPECT_CALL(mock, Sign(-4, _));
EXPECT_CALL(mock, Sign(0, _));
EXPECT_CALL(mock, Sign(1, _)).Times(2);
EXPECT_CALL(mock, Sign(2, _));
EXPECT_CALL(mock, Sign(3, _));
EXPECT_CALL(mock, Sign(5, _));

在另一个示例中,假设 Sign 返回int,如果您编写

ON_CALL(mock, Sign(Gt(0), _)).WillByDefault(Return(1));
EXPECT_CALL(mock, Sign(10, _));

调用mock.Sign(10)将返回 1,因为ON_CALLEXPECT_CALL指定的调用提供默认行为。但是如果你写

EXPECT_CALL(mock, Sign(Gt(0), _).WillRepeatedly(Return(1));
EXPECT_CALL(mock, Sign(10, _));

mock.Sign(10, p)的调用将返回 0。它将与第二个期望相匹配。该期望不指定显式操作,gMock 将为其生成默认操作。该默认操作是返回返回类型的默认值,对于int为 0。在这种情况下,第一个期望将被完全忽略。

ON_CALL(mock, methodX(_)).WillByDefault(Return(0x01));
EXPECT_CALL(mock, methodX(_)).WillRepeatedly(Return(0x01));

正如你所说,这两条线在做完全相同的事情,因此根本没有区别。使用任一方式根据需要设置默认操作。

但是,有一个逻辑上的区别:

  • ON_CALL(mock, methodX(_)).WillByDefault(Return(0x01));意味着可能会调用该方法,如果发生这种情况,则每次调用都将返回0x01
  • EXPECT_CALL(mock, methodX(_)).WillRepeatedly(Return(0x01));表示预期将调用该方法,并且每次调用都将返回0x01

顺便说一下,他们的备忘单中有一个设置默认操作,上面写着:

若要自定义特定方法的默认操作,请使用 ON_CALL():

ON_CALL(mock_object, method(matchers))
.With(multi_argument_matcher)  ?
.WillByDefault(action);

以下是 gMock 食谱中解释的关于ON_CALLEXPECT_CALL之间最重要差异的"官方"解释。

基本上有两种构造来定义模拟对象的行为:ON_CALLEXPECT_CALL

区别?

ON_CALL定义了调用模拟方法时会发生什么,但并不意味着对被调用的方法有任何期望

EXPECT_CALL不仅定义了行为,而且还设置了一个期望,即必须使用给定的参数在给定的次数内(以及指定顺序时也按给定的顺序)调用该方法

既然EXPECT_CALL做得更多,那不是比ON_CALL更好吗?

没有。 每个EXPECT_CALL都会对待测试代码的行为添加一个约束。 拥有比必要的更多的约束是不好的 - 甚至比没有足够的约束更糟糕。

这可能是违反直觉的。 验证更多的测试怎么会比验证更少的测试更糟糕? 验证不是测试的全部意义吗?

答案在于测试应该验证什么。一个好的测试可以验证代码的协定。 如果测试过度指定,则不会为实现留出足够的自由度。因此,在不破坏契约的情况下更改实现(例如重构和优化)应该完全没问题,可以破坏此类测试。 然后,您必须花时间修复它们,只是在下次更改实现时再次看到它们损坏。

请记住,不必在一次测试中验证多个属性。 事实上,在一次测试中只验证一件事是一种很好的风格。如果你这样做,一个错误可能只会破坏一两个测试,而不是几十个(你更愿意调试哪种情况?)。 如果您也习惯于为测试提供描述性名称来说明它们验证的内容,那么您通常可以轻松地仅从测试日志本身猜出问题所在。

因此,默认情况下使用ON_CALL,并且仅在实际打算验证是否已进行调用时才使用EXPECT_CALL

一个区别是ON_CALL行为(默认行为)和EXPECT_CALL期望的清除方式不同。

https://github.com/google/googletest/blob/master/googlemock/docs/cheat_sheet.md#verifying-and-resetting-a-mock

using ::testing::Mock;
...
// Verifies and removes the expectations on mock_obj;
// returns true if and only if successful.
Mock::VerifyAndClearExpectations(&mock_obj);
...
// Verifies and removes the expectations on mock_obj;
// also removes the default actions set by ON_CALL();
// returns true if and only if successful.
Mock::VerifyAndClear(&mock_obj);

这可用于在测试中的某个时刻清除预期,但仍保留模拟对象的默认行为。请注意,StrictMock对象的情况并非如此,因为它们不允许测试在没有实际期望的情况下通过,即使使用ON_CALL定义了默认行为设置也是如此。

请参阅此处 https://github.com/google/googletest/blob/master/docs/gmock_cook_book.md#knowing-when-to-expect-useoncall

基本上有两种构造来定义模拟对象的行为:ON_CALL 和 EXPECT_CALL。区别?ON_CALL定义了调用模拟方法时会发生什么,但并不意味着对被调用的方法有任何期望。EXPECT_CALL不仅定义了行为,而且还设置了一个期望值,即该方法将在给定的次数内使用给定的参数调用(并且在指定顺序时也按给定的顺序调用)。

我很困惑,没有答案提到最重要的术语:无趣的电话

EXPECT_CALL信号,表示函数调用是相关/有趣的。如果预期的函数调用与您的规范(调用次数、参数等)不匹配,则这是一个失败的期望,会导致测试用例失败。

如果使用ON_CALL而没有对相应的功能进行任何进一步的EXPECT_CALL,那么这表明该函数是"无趣的",并且进一步的行为在很大程度上取决于我们使用的模拟类型(strict/naggy/nice),范围从忽略调用到测试用例失败。

将EXPECT_CALL用于测试目的所必需的内容,使用 ON_CALL 指定浏览待测试代码所需的操作,如果没有特别要求,则通过跳过EXPECT_CALL来避免过度规范以及ON_CALL不相关的函数(并使用 GMock 定义的默认行为)。