你应该只模仿你拥有的类型吗?

时间:2009-12-15 10:00:30

标签: mocking

我通过Mark Needham的TDD: Only mock types you own条目阅读并想知道这是否是最佳做法?

请注意,他不是反对嘲笑,而是直接反对嘲笑 - 他确实说过写一个包装并嘲笑这很好。

7 个答案:

答案 0 :(得分:28)

我的回答是“不”。你应该在给定单元测试的上下文中模拟任何有意义的东西。如果你“拥有”模拟类型,那应该没关系。

现在,在Java或.NET环境中,一切(我真的意味着一切)都可以很容易地被嘲笑。因此,没有技术上的理由去首先编写额外的包装代码。


我最近(2010年11月)一直在思考的其他一些想法,表明“只有你自己的模拟类型”的不合逻辑可能是:

  1. 假设您执行为第三方API创建包装器,然后在单元测试中模拟包装器。但是,稍后您认为包装器可以在另一个应用程序中重用,因此您将其移动到单独的库中。所以现在包装器不再由你“拥有”(因为它被用在多个应用程序中,可能由不同的团队维护)。开发人员应该为旧的包装器创建一个新的包装器吗?并继续递归,添加一层层基本无用的代码?
  2. 假设其他人已经为一些非平凡的API创建了一个很好的包装器,并使其可用作可重用的库。如果所说的包装器正是我需要的特定用例,我应该首先为包装器创建一个包装器,使用几乎相同的API,这样我就会“拥有”它吗?!?
  3. 对于具体而实际的示例,请考虑Apache Commons Email API,它只不过是标准Java Mail API的包装器。由于我不拥有它,每当我为需要发送电子邮件的类编写单元测试时,我是否应该始终为Commons Email API创建一个包装器?

答案 1 :(得分:22)

取决于你是否意味着模拟或模拟......

鉴于您只是使用模拟框架(例如Mockito)来创建存根,那么创建您不拥有的类型的存根是完全可以合理的。

但是,如果您使用模拟框架(例如Mockito)来创建mock™对象,那么您最好遵循mock™布道者的建议。就个人而言,我对这一运动失去了联系,所以我无法告诉你Mark Needham的建议是否被视为犹太教。

除此之外,马克写的关于在Hibernate中嘲笑EntityManagers的内容听起来本身就是合理的。但是我怀疑我们可以从这个特定的案例中概括出一个像“永远不会嘲笑你不拥有的类型”这样的规则。有时它可能有意义,有时不是。

答案 2 :(得分:14)

我喜欢这个问题的explanation the Mockito project gives

  

不要嘲笑你不拥有的类型!

     

这不是一条强硬路线,但越过这条线可能会有   反响! (很可能会)

     
      
  1. 想象一下嘲笑第三方lib的代码。在第三个库的特定升级之后,逻辑可能会改变一点,但是测试   套件将执行得很好,因为它被嘲笑。所以后来,   认为一切都很好,毕竟建筑墙是绿色的,   该软件已部署并...... Boom
  2.   
  3. 这可能表示当前设计与第三方库没有足够的分离。
  4.   
  5. 另一个问题是第三方lib可能很复杂,需要很多模拟才能正常工作。这导致过度   指定的测试和复杂的固定装置,这本身就妥协了   紧凑和可读的目标。或者不包含代码的测试   因为模拟外部系统的复杂性。
  6.         

    相反,最常见的方法是在外部创建包装器   lib / system,尽管应该知道抽象的风险   泄漏,过多的低级API,概念或例外   超出包装器的边界。为了验证集成   与第三方库,编写集成测试,并制作它们   尽可能紧凑和可读。

答案 3 :(得分:8)

我打算说“不”,但快速浏览一下博文,我可以看到他的内容。

他特别谈到在Hibernate中模拟EntityManagers。我反对这个。 EntityManagers应该隐藏在DAO(或类似的)中,DAO应该被嘲笑。测试一行调用EntityManager完全浪费你的时间,一旦发生任何变化就会中断。

但是,如果你确实有第三方代码,你想测试你如何与它进行交互。

答案 4 :(得分:1)

恕我直言,所有权问题无关紧要。

相关问题是耦合之一,即您的测试代码指定了什么。您当然不希望测试代码指定您碰巧使用的某些库的API的详细信息。这是你得到的,例如使用Mockito直接在您的测试类中模拟库。

这个问题的widespread solution proposal是围绕库创建一个包装器然后模拟包装器。但这有以下缺点:

  • 未对包装内的代码进行测试。
  • 包装器可能是不完美的抽象,因此可能需要更改包装器的API。如果你在许多测试中嘲笑包装器,你必须调整所有这些测试。

相反,我建议将生产代码中的接口与测试完全分离。不要直接将模拟放入测试代码中,而是创建一个单独的存根类来实现或模拟生产界面。然后向存根添加第二个接口,允许测试执行必要的设置或断言。然后,您只需要调整一个类,以防生产界面发生变化 - 您甚至可以模拟/存储复杂或频繁更改的库的接口。

注意:所有这些都假设实际上必须使用模拟或存根。我这里没有讨论这个问题,因为它不在OP的问题范围内。但是真的问问自己是否必须使用模拟/存根。根据我的经验,他们被过度使用......

答案 5 :(得分:0)

我同意马克的说法。你不能不幸地嘲笑一切,有些东西你不想嘲笑,只因为你正常使用它是一个黑盒子。

我的经验法则是模拟可以使测试快速但不会使测试变薄的事情。请记住,并非所有假货都是相同的Mocks are not Stubs

答案 6 :(得分:0)

我当然是少数人,但我认为Mocking是一个代码嗅觉并且如果可能的话使用依赖注入。理由是,模拟基本上是测试一些难以测试的代码的解决方法。模拟会削弱测试,因为它们(最好)像特定版本的库一样。如果库发生了变化,那么您的测试将失去所有检查值。

你可以看到上面的内容,我正在使用马克·李约瑟自己的论点,但不是因为你说你不应该嘲笑你不拥有的对象,而是你不应该嘲笑......

好吧,如果依赖注入不是一个选项,那么让我们模拟......但是你必须明白你的测试是假的,并且不会像生产代码那样。这不是一个真正的单元测试,只是一个部分伪造的测试。如果可能的话,你可以通过添加检查行为与模拟对象一样的行为的测试来减少它。