如何使用Mocks?
当我最初被介绍给Mocks时,我感到主要目的是模拟来自外部数据源的对象。这样,我不必维护自动化的单元测试测试数据库,我可以伪造它。
但是现在我开始以不同的方式思考它。我想知道Mocks是否可以更有效地将测试方法与自身之外的任何事物完全隔离。不断浮现的图像是我们在绘画时使用的背景。我们想防止涂料泛滥成灾。我仅测试该方法,并且只想知道它对这些伪造的外部因素有何反应?
用这种方法执行此操作似乎非常繁琐,但是我看到的好处是,测试失败时,它是因为它被拧紧了而不是降低了16层。但是现在我必须进行16个测试才能获得相同的测试覆盖率,因为每个测试都将单独进行测试。另外,每个测试变得更加复杂,并且与所测试的方法更加紧密地联系在一起。
这对我来说感觉不错,但它看起来也很残酷,所以我有点想知道别人的想法。
解决方案
回答
是的我同意。我认为模拟有时会很痛苦,但通常是必须的,这样才能使测试真正成为单元测试,即,只有可以使测试涉及的最小单元正在测试中。这使我们可以消除可能影响测试结果的任何其他因素。我们最终会进行更多的小型测试,但是找出代码问题所在的步骤变得非常容易。
回答
不要走黑暗的路加大师。 :)不要嘲笑一切。我们可以,但不应该...这就是原因。
- 如果我们继续单独测试每种方法,那么在将它们全都放在BIG BANG上时,我们会感到惊讶和艰巨的工作。我们构建对象,以便它们可以一起解决更大的问题。就其本身而言,它们是微不足道的。我们需要知道所有协作者是否都按预期工作。
- 嘲讽通过引入重复来使测试变脆-是的,我知道这听起来令人震惊。对于我们希望设置的每个模拟,在方法签名存在的位置有n个。实际代码和模拟期望(在多个测试中)。更改实际代码更容易...更新所有模拟期望是乏味的。
- 测试现在可以了解内部实施信息。因此,测试取决于我们选择实施解决方案的方式……不好。测试应该是可以由多种解决方案满足的独立规范。我应该可以自由地在代码块上按Delete键并重新实现,而不必重写测试套件。因为要求仍然保持不变。
最后,我会说:"如果它像鸭子一样嘎嘎叫,走路像鸭子一样,那么它可能就是鸭子"。 *使用模拟来抽象出有问题的孩子,例如IO操作,数据库,第三方组件等。像盐一样,其中有些是必要的..太多和:x *
这是基于状态与基于迭代的测试的圣战。Google搜索将为我们提供更深入的了解。
澄清:我遇到了一些阻力集成测试在这里:)因此,请澄清一下我的立场。
- 在"验收测试" /集成领域中没有模拟。我们只会在"单元测试"世界中找到它们。.这就是我在这里关注的重点。
- 验收测试是不同的,非常需要-不要轻视它们。但是单元测试和验收测试是不同的,应该保持不同。
- 组件或者包装内的所有协作者都无需彼此隔离。就像过度优化的微观优化一样。它们的存在是为了共同解决一个问题。
回答
我的理念是我们应该编写可测试的代码以适合测试,
不编写适合代码的测试。
至于复杂性,我认为测试应该编写简单,仅仅是因为我们编写了更多测试。
我可能会同意,如果我们要模拟的类没有测试套件,这可能是一个好主意,因为如果它们确实具有适当的测试套件,我们将知道没有隔离的问题所在。
我使用模拟对象的大多数时间是当我正在为其编写测试的代码紧密耦合(阅读:不良设计)时,当它们依赖的类不可用时,我不得不编写模拟对象。当然,可以对模拟对象进行有效使用,但是如果代码需要使用它们,那么我将再来看一下该设计。
回答
我建议我们看一下Martin Fowler的文章Mocks Are n't Stubs,获得对Mocks更具权威性的处理方式,而我对此无能为力。
模拟的目的是在独立于依赖项的情况下对代码进行单元测试,以便我们可以在"单元"级别上真正地测试一段代码。被测试的代码是真正的交易,它所依赖的其他所有代码段(通过参数或者依赖项注入等)都是"模拟"(空的实现,在调用其方法之一时始终返回期望值)。
起初,模拟似乎很乏味,但是一旦掌握了使用技巧,它们就会使单元测试变得更加轻松和强大。大多数语言都有Mock库,这使得嘲笑变得相对琐碎。如果我们使用的是Java,我将推荐我最喜欢的个人软件:EasyMock。
让我结束这个想法:我们也需要集成测试,但是拥有大量的单元测试可以找出存在缺陷的组件。
回答
是的,这是使用模拟进行测试的缺点。我们需要做很多工作,因为它感觉很残酷。但这是单元测试的本质。如果我们不模拟外部资源,如何隔离测试?
另一方面,我们正在嘲笑缓慢的功能(例如数据库和I / O操作)。如果测试运行得更快,那将使程序员满意。没有什么比等待真正缓慢的测试更痛苦的了,在我们尝试实现一项功能时,它需要十多秒钟才能完成运行。
如果项目中的每个开发人员都花时间编写单元测试,那么这16层(间接)将不是什么大问题。希望我们应该从一开始就拥有该测试覆盖率,对吗? :)
另外,不要忘记在协作对象之间编写功能/集成测试。否则,我们可能会错过一些东西。这些测试不需要经常运行,但是仍然很重要。
回答
在某种程度上,是的,模拟旨在用于模拟外部数据源,例如数据库或者Web服务。然而,如果要设计松散耦合的代码,则可以在更细粒度的范围内,几乎可以任意地在代码中画线,以了解在任何时候可能是"外部系统"。参加一个我目前正在从事的项目:
当有人尝试签入时,CheckInUi将CheckInInfo对象发送到CheckInMediator对象,该对象使用CheckInValidator对其进行验证,然后,如果确定,它将使用CheckInInfoAdapter用CheckInInfo填充名为Transaction的域对象,然后将Transaction传递给ITransactionDao的实例.SaveTransaction()的持久性。
我现在正在编写一些自动化集成测试,显然CheckInUi和ITransactionDao是外部系统的窗口,应该嘲笑它们。但是,有谁说在某个时候CheckInValidator不会调用Web服务?这就是为什么在编写单元测试时,我们会假设除类的特定功能之外的所有内容都是外部系统。因此,在CheckInMediator的单元测试中,我模拟了与之对话的所有对象。
编辑:Gishu在技术上是正确的,不是所有内容都需要模拟,我不模拟模拟CheckInInfo,因为它只是数据的容器。但是,我们可能会视作外部服务的任何东西(几乎是任何可以转换数据或者具有副作用的东西)都应该被嘲笑。
我喜欢一个比喻,就是将一个适当松散耦合的设计想像成一个领域,周围人们围坐在一起玩捉迷藏的游戏。当有人传球时,他可能会向下一个人投掷完全不同的球,他甚至可能会连续向不同的人投掷多个球,或者投掷一个球,并等待将其返回,然后再将其投掷给另一个人。这是一个奇怪的游戏。
现在,作为他们的教练兼经理,我们当然想要检查团队的整体运作方式,以便进行团队练习(集成测试),但是我们还需要让每个球员自己针对逆止器和投球机进行练习(单元测试与模拟)。该图片丢失的唯一片段是模拟预期,因此我们的球涂有黑色焦油,因此当它们撞到支撑架时会弄脏支撑架。每个逆止器都有一个人要针对的"目标区域",如果在练习结束时目标区域内没有黑标,则说明我们知道出了点问题,并且该人需要对其技术进行调整。
真正花时间去正确地学习它,那天我才知道Mocks是一个巨大的时刻。将其与控制容器的反转结合使用,我再也不会回头了。
附带一提,我们的一位IT人员刚进来,给了我一台免费的笔记本电脑!
回答
就像有人说过的那样,如果我们模拟所有内容以比要测试的类更详细地隔离,我们将放弃在被测代码中强制执行内聚。
请记住,模拟具有基本优势,即行为验证。这是存根不提供的东西,也是导致测试更脆弱(但可以提高代码覆盖率)的另一个原因。
回答
发明了嘲讽部分是为了回答这个问题:如果没有吸气剂或者吸气剂,我们将如何对物体进行单元测试?
如今,推荐的做法是模拟角色而不是对象。使用Mocks作为设计工具来谈论协作和职责分离,而不是"智能存根"。
回答
模拟对象是(1)通常用作隔离测试代码的一种方法,但keithb已经指出2)对于"关注协作对象之间的关系"很重要。本文提供了与以下主题有关的一些见解和历史:使用模拟对象的责任驱动设计。