【问题标题】:Is it unnecessary to verify the same methods as the methods being mocked in Mockito?是否不需要验证与在 Mockito 中模拟的方法相同的方法?
【发布时间】:2016-02-08 04:51:42
【问题描述】:

我经常看到验证的方法与 Mockito 中被模拟的方法相同(示例如下)。在这些情况下拨打Mockito.verify() 有什么额外的好处吗?

//mock method
FooService fs = mock(FooService.class);
when(fs.getFoo()).thenReturn("foo");

//method under test
fs.doSomething();

//verify method
verify(fs).getFoo();

如果没有调用fs.getFoo(),该方法应该会失败。那么为什么要打电话给verify?如果您需要在验证中使用ArgumentCaptor 来断言参数,我看到了好处;除了 ArgumentCaptor 案例,它只是不必要的吗?

【问题讨论】:

  • 我会说这是必要的,因为如果您突然将您的doSomething() 更改为不再调用getFoo() 或只是评论它怎么办?如果您还没有验证方法调用,您将如何知道这一点。
  • @TajAhmed 如果不执行 verify() 就没有调用模拟方法,难道没有办法使测试失败吗?
  • 当您在测试类方法中使用模拟方法的输出时,这是可能的。然后,您可以通过断言返回值来验证模拟数据是否已准备好。根据您的测试方法是否返回任何值,这样做可能/可能不可能。所以我建议使用 verify 来保证方法被调用。
  • 我认为Mockito.when 应该使用术语“存根”而不是“模拟”(参见docs.mockito.googlecode.com/hg/org/mockito/Mockito.html#2)。

标签: java unit-testing mockito


【解决方案1】:

Mockito 文档一再表示它通常是多余的。这在verify(T)'s Javadoc 中逐字显示为Mockito's main class Javadoc section 2 代码块中的多个单行cmets:

虽然可以验证存根调用,但通常它只是多余的。 如果您的代码关心get(0) 返回的内容,那么就会出现其他问题(通常甚至在verify() 执行之前)。 如果您的代码不关心 get(0) 返回的内容,则不应将其存根。不服气?见here

请注意,最初链接的文章“Asking and Telling”是由 Mockito 创始人 Szczepan Faber 撰写的,在 Mockito 的设计中可视为权威文档。摘自那篇文章:

我真的必须重复同样的表达吗?毕竟,存根交互是隐式验证的。我自己的代码的执行流程完全免费Aaron Jensen 还注意到:

如果您正在验证,则不需要存根,除非该方法当然返回对您的测试(或代码)流程至关重要的东西,在这种情况下,您实际上不需要验证,因为flow 会得到验证。

回顾一下:没有重复的代码

但是如果一个有趣的交互具有询问和告知的特点呢?我必须在 stub() 和 verify() 中重复交互吗?我最终会得到重复的代码吗?并不真地。在实践中:如果我存根,那么它是免费验证的,所以我不验证如果我验证那么我不关心返回值,所以我不存根。不管怎样,I don’t repeat myself。不过理论上,我可以想象一种罕见的情况,我确实验证了存根交互,例如确保存根交互恰好发生 n 次。但这是行为的一个不同方面,显然是一个有趣的方面。因此,我想明确一点,我非常乐意牺牲一行额外的代码……

最新版本的 Mockito(自此问答发布后发布)添加了一些附加功能,这些功能使 allowdefault to 更严格的模拟风格。尽管如此,普遍的期望是通过仅验证您无法通过断言或成功测试完成来确认的内容来避免脆弱性。

总的来说,Mockito 的设计是让测试尽可能灵活,编码不是针对实现,而是针对您正在测试的方法的规范。尽管您偶尔会在函数规范中看到方法调用(“向服务器提交 RPC”或“立即调用传递的 LoginCallback”),但您更有可能想要验证可以验证的后置条件从存根推断:检查getFoo 是否被调用并不是规范的一部分,只要您存根getFoo 以返回“foo”并且数据存储包含一个对象,其相应的属性设置为“foo” ”。


简而言之,仅显式验证精心设计的存根和后置条件断言无法隐含的交互被认为是良好的 Mockito 风格。它们可能是对其他无法衡量的副作用的良好调用——记录代码、线程执行器、ArgumentCaptors、多个方法调用、回调函数——但通常不应用于存根交互。

【讨论】:

  • “问和说”链接不再有效。如果没有它,我会感到困惑,因为测试的目的是测试代码中的意外更改是否会使测试失败。而这个概念似乎为了减少口头测试而牺牲了它?许多其他模拟框架用一行代码完成这两项任务,不需要重复。
  • @EatonEmmerich 我已经更新了链接。 Mockito 作为一个框架的最初观点是,意外的代码更改应该不会自动使测试失败,因为一个好的测试不应该是一个更改检测器,它应该只是将实现保持在它自己的规范中,即使是实施不断发展。可以像在 Mockito 的灵感/祖先 EasyMock 中那样“默认严格”模拟,但 Mockito 出于这个原因故意分歧。自从我写下我的答案后,Mockito 的“宽大”新概念确实修改了最初的愿景,但我今晚无法编辑。
  • 我挖出了“问与说”的文章链接。看起来 Faber 的旧博客的域发生了变化,这被他有一个不包含这些旧帖子的新博客这一事实所掩盖。 szczepiq.wordpress.com/2008/04/26/asking-and-telling
  • @Planky 谢谢,是的。 URL 架构发生了一些变化,但帖子仍然存在。我已经编辑了新的。
【解决方案2】:

在您给出的具体示例中(这并不是一个完整的示例,但足以让您的问题清楚),我认为没有理由验证被存根的调用。

我认为 verify 旨在产生副作用的调用和 stub 旨在提供数据的调用(使用when)是很好的设计。在大多数情况下,两者都做会使设计变得不那么清晰,测试的可读性也会降低。

如果提供数据且没有副作用的调用接收到由被测方法生成或修改的参数值(例如,为检索值而合成的键),则验证可能 em> 很有用(即使参数足够简单以至于您不需要ArgumentCaptor)。您可以使用特定参数或自定义匹配器存根,这可能足以测试这种情况(例如when(fs.getFoo("generatedKey1234")).thenReturn("foo1234")),但检查verify 中的参数可以提高测试的可读性 - 所以它是判断电话。

【讨论】:

  • 您能否详细说明“如果提供数据且没有副作用的调用作为参数值接收......”这句话我不明白。
  • 如果您的测试方法(我们称之为 MUT)调用协作者的 getter 方法(如您的示例中的 getFoo()),您只需对 getter 进行存根以验证 MUT 的行为是否符合预期.如果 MUT 调用协作者根据传入 MUT 的参数检索值(例如,根据传入 MUT 的用户名从存储中检索用户),这也是存根的明显案例,因为 MUT 只是获取给定的内容并使用它从合作者那里获取数据。
  • 继续我上面的评论,您不清楚的情况:MUT 不仅将收到的参数传递给合作者,而且实际上修改它们或生成它们或从合作者。 MUT 创建或检索至少一个它在被调用之前没有的值,并使用该值从合作者(为测试模拟)检索数据。在这种情况下,您需要验证是否正确创建或检索了新创建/检索的值(它本身不是由 MUT 返回的)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多