如何使用Mocks?

时间:2008-09-12 14:59:14

标签: unit-testing mocking

当我最初被介绍给Mocks时,我觉得主要目的是模拟来自外部数据源的对象。这样我就不必维护自动化的单元测试测试数据库,我可以假装它。

但现在我开始以不同的方式思考它。我想知道Mocks是否更有效地用于将测试方法与其他任何东西完全隔离。不断浮现在脑海中的图像是您在绘画时使用的背景。你想让油漆不要遍布一切。我只测试那种方法,我只想知道它对这些伪造的外部因素有何反应?

以这种方式做这似乎非常繁琐,但我看到的优势是当测试失败时,因为它被拧紧而不是16层。但是现在我必须进行16次测试以获得相同的测试覆盖率,因为每个部分都将被隔离测试。此外,每项测试都变得更加复杂,并且与它正在测试的方法更加紧密相关。

对我来说这是对的,但它似乎也很残酷,所以我想知道别人怎么想。

9 个答案:

答案 0 :(得分:19)

我建议你看一下Martin Fowler的文章Mocks Aren't Stubs,以获得比Mocks更具权威性的治疗方法。

模拟的目的是单独测试代码,而不依赖于依赖项,这样您就可以在“单元”级别真正测试一段代码。正在测试的代码是真正的交易,它所依赖的每一段代码(通过参数或依赖注入等)都是一个“模拟”(一个空的实现,它总是在调用其中一个方法时返回预期的值。)

Mocks最初可能看起来很乏味,但是一旦你掌握了它们,它们就会使单元测试变得更加容易和强大。大多数语言都有模拟库,这使得模拟相对微不足道。如果您使用的是Java,我会推荐我个人最喜欢的:EasyMock.

让我完成这个想法:你也需要集成测试,但是有大量的单元测试可以帮助你找出哪个组件包含一个bug,如果存在的话。

答案 1 :(得分:15)

不要沿着黑暗的道路走向卢克大师。 :)不要嘲笑一切。你可以,但你不应该......这就是原因。

  • 如果您继续单独测试每种方法,那么当您将它们全部放在一起时,您会感到意外并为您做好工作 BIG BANG 。我们构建对象以便他们可以一起工作来解决更大的问题。他们自己是微不足道的。您需要知道所有协作者是否按预期工作
  • 模拟通过引入重复使测试变得脆弱 - 是的,我知道这听起来令人担忧。对于您设置的每个模拟,存在方法签名存在的n个位置。实际代码和您的模拟期望(在多个测试中)。更改实际代码更容易......更新所有模拟期望是乏味的。
  • 您的测试现在可以了解内部人员实施信息。所以你的测试取决于你如何选择实施解决方案......糟糕。测试应该是一个独立的规范,可以通过多种解决方案来满足。我应该可以自由地在一段代码上按删除并重新实现,而不必重写测试套件。因为要求仍然保持不变。

要结束,我会说“如果它像鸭子一样嘎嘎叫,像鸭子一样走路,那么它可能是一只鸭子” - 如果感觉不对,那可能就是这样。 *使用模拟来抽象问题孩子,如IO操作,数据库,第三方组件等。像盐,有些是必要的..太多了:x *
这是基于国家战争和基于Iteraction的测试的神圣战争。谷歌搜索将为您提供更深入的见解。

澄清:我正在接受一些阻力w.r.t.这里的集成测试:)所以澄清我的立场..

  • 模拟不参与'验收测试'/集成领域。你只能在单元测试世界中找到它们。这是我的重点。
  • 验收测试不同,需要非常 - 不要轻视它们。但是单元测试和验收测试是不同的,应该保持不同。
  • 组件或包中的所有协作者不需要彼此隔离。就像Overkill的微优化一样。它们的存在是为了解决一起 ..凝聚力的问题。

答案 2 :(得分:2)

是的,我同意。我认为模拟有时是痛苦的,但通常是必要的,因为你的测试真正成为单元测试,即只有你可以让你的测试关注的最小单元正在测试中。这允许您消除可能影响测试结果的任何其他因素。你最终会得到更多的小测试,但是在你的代码出现问题的地方变得容易得多。

答案 3 :(得分:1)

我的理念是你应该编写可测试的代码以适应测试,
不写测试以适应代码。

至于复杂性,我认为测试应该很容易编写,只是因为你编写了更多的测试。

我可能会同意,如果您正在嘲笑的类没有测试套件,这可能是一个好主意,因为如果他们确实有一个合适的测试套件,您就会知道问题在哪里而没有隔离。

我们大部分时间用于模拟对象的时候,我正在编写测试的代码是如此紧密耦合(读取:糟糕的设计),我必须在他们依赖的类不是时编写模拟对象可用。当然有模拟对象的有效用途,但如果你的代码需要,我会再看一下设计。

答案 4 :(得分:1)

是的,这是用模拟测试的缺点。你需要投入大量的工作才能感觉到残酷。但这是单元测试的本质。如果不模拟外部资源,如何单独测试某些内容?

另一方面,你正在嘲笑缓慢的功能(例如数据库和i / o操作)。如果测试运行得更快,那么程序员就会感到高兴。没有什么比等待真正慢的测试更令人痛苦的了,当你尝试实现一个功能时,需要10秒以上才能完成运行。

如果项目中的每个开发人员都花时间编写单元测试,那么这16层(间接)就不会出现太多问题。希望你从一开始就应该有测试覆盖率,对吧? :)

另外,不要忘记在协作中编写对象之间的函数/集成测试。否则你可能会错过一些东西。这些测试不需要经常运行,但仍然很重要。

答案 5 :(得分:1)

在一个范围内,是的,模拟意味着用于模拟外部数据源,例如数据库或Web服务。在更细粒度的范围内,如果您正在设计松散耦合的代码,那么您可以在整个代码中几乎任意地绘制线条,以便在任何时候可能是“外部系统”。参加我目前正在进行的项目:

当有人尝试登记时, CheckInUi 会将 CheckInInfo 对象发送到 CheckInMediator 对象,该对象使用 CheckInValidator对其进行验证,然后如果没问题,它会使用 CheckInInfo CheckInInfo 填充名为交易的域对象,然后传递事务 ITransactionDao.SaveTransaction()的实例以实现持久性。

我现在正在编写一些自动集成测试,显然 CheckInUi ITransactionDao 是外部系统的窗口,它们就是那些哪个应该被嘲笑。但是,在某些时候, CheckInValidator 将不会调用Web服务?这就是为什么当您编写单元测试时,您认为除了您的类的特定功能之外的所有内容都是外部系统。因此,在 CheckInMediator 的单元测试中,我模拟了它与之对话的所有对象。

编辑: Gishu在技术上是正确的,并非所有东西都需要被嘲笑,我不会例如模拟 CheckInInfo ,因为它只是一个数据容器。但是,任何你可以看作外部服务的东西(几乎任何转换数据或有副作用的东西)都应该被嘲笑。

我喜欢的类比是将一个适当松散耦合的设计想象成一个人们站在它周围玩耍的游戏。当有人传球时,他可能会向另一个人投掷一个完全不同的球,他甚至可能会连续向不同的人投掷多个球,或者扔一个球并等待接收它然后扔给另一个人。这是一场奇怪的比赛。

现在作为他们的教练和经理,你当然想要检查你的团队如何整体运作,这样你就可以进行团队训练(整合测试),但是你也让每个球员独自练习对抗逆止器和投球机(用模拟进行单元测试)。这张照片缺失的唯一部分就是模拟的期望,所以我们的球被涂上了黑色的焦油,所以当它们撞到它时会弄脏逆止器。每个逆止器都有一个人所针对的“目标区域”,如果在练习结束时,目标区域内没有黑色标记,则表示出现问题并且该人需要调整其技术。

真正花时间学习它,我知道Mocks是一个巨大的时刻。将它与控制容器的反转相结合,我永远不会回去。

另一方面,我们的一位IT人员刚进来给了我一台免费的笔记本电脑!

答案 6 :(得分:1)

正如有人之前所说,如果你模仿所有内容来分离比你正在测试的类更精细,那么你就放弃了对你正在测试的代码的强制凝聚力。

请记住,模拟具有一个基本优势,即行为验证。这是存根不提供的东西,也是使测试更加脆弱的另一个原因(但可以提高代码覆盖率)。

答案 7 :(得分:1)

模拟的部分发明是answer the question:如果对象没有吸气剂或制定者,你会如何对它们进行单元测试?

这些天,recommended练习是模拟角色而不是对象。使用Mocks作为设计工具来谈论协作和职责分离,而不是“智能存根”。

答案 8 :(得分:0)

模拟对象通常用作隔离被测代码的手段,但正如keithb已经指出的那样,对于“focus on the relationships between collaborating objects”非常重要。本文提供了与该主题相关的一些见解和历史:Responsibility Driven Design with Mock Objects