为什么嘲笑课是如此糟糕?

我最近和一位同事讨论了嘲笑。 他说嘲笑课是非常糟糕的,不应该做,只有less数情况。

他说只有接口应该被嘲笑,否则就是一个架构错误。

我想知道为什么这个声明(我完全相信他)是如此正确? 我不知道,希望被说服。

我有没有想到嘲笑的地方(是的,我读了马丁·福勒的文章 )

模拟用于协议testing – 它testing你将如何使用API​​,以及当API作出相应反应时你将如何反应。

理想情况下(至less在许多情况下),该API应该被指定为一个接口而不是一个类 – 一个接口定义了一个协议,一个类至less定义了一个实现的一部分。

实际上,嘲笑框架往往对嘲笑类有限制。

根据我的经验,嘲笑有点过度使用 – 通常你并不真正感兴趣的确切的互动,你真的想要一个存根 …但嘲笑的框架可以用来创build存根,你陷入创造脆性testing的陷阱嘲笑而不是扼杀。 这是一个很难平衡的权利,虽然。

恕我直言,你的同事的意思是,你应该编程到一个接口,而不是一个实现 。 如果你发现自己经常嘲笑课程,那么在devise你的架构时,这是一个打破以前原则的标志。

模拟类(与模拟接口相反)是不好的,因为模拟在后台仍然有一个真实的类,它是inheritance的, 在testing过程中可能会执行真正的实现

当你模拟(或者存根或者其他)接口的时候,你不需要执行代码的风险就是你真正想要模拟的。

嘲笑课也会迫使你把所有可能被嘲笑的东西变成虚拟的 ,这是非常侵入性的,并可能导致糟糕的课堂devise

如果你想分解类,他们不应该彼此了解,这就是为什么他们之一嘲笑(或存根或其他)是有道理的。 所以无论如何都推荐对接口进行实现,但是这在别人的足够提及的地方。

通常你会想要模拟一个接口。

虽然有可能嘲笑一个普通的课, 但它往往会影响你的课堂devise太多的可testing性。 无障碍等问题,方法是否虚拟等都将由嘲笑课堂的能力决定 ,而不是真正的OO问题。

有一个叫做TypeMock Isolator的伪造库,可以让你绕开这些限制(有蛋糕,吃蛋糕),但是它非常昂贵。 更好的devise可testing性。

我build议尽可能远离嘲讽框架。 同时,我会尽可能地推荐使用模拟/假物体进行testing。 这里的诀窍是你应该创build内置的假对象与真实的对象。 我在一篇博客文章中详细解释: http : //www.yegor256.com/2014/09/23/built-in-fake-objects.html

编辑:既然你已经澄清,你的同事意味着模拟是坏的,但模拟接口不是,下面的答案是过时的。 你应该参考这个答案 。

我正在谈论Martin Fowler所定义的模拟和存根,我想这也是你的同事的意思。

嘲笑是不好的,因为它可能导致testing的过度规范。 尽可能使用存根,避免模拟。

这里是模拟和存根之间的差异(从上面的文章):

然后我们可以像这样在存根上使用状态validation。

class OrderStateTester... public void testOrderSendsMailIfUnfilled() { Order order = new Order(TALISKER, 51); MailServiceStub mailer = new MailServiceStub(); order.setMailer(mailer); order.fill(warehouse); assertEquals(1, mailer.numberSent()); } 

当然这是一个非常简单的testing – 只是发送了一条消息。 我们没有testing过它是发送给合适的人还是正确的内容,但是这样做是为了说明这一点。

使用嘲笑这个testing看起来完全不同。

 class OrderInteractionTester... public void testOrderSendsMailIfUnfilled() { Order order = new Order(TALISKER, 51); Mock warehouse = mock(Warehouse.class); Mock mailer = mock(MailService.class); order.setMailer((MailService) mailer.proxy()); mailer.expects(once()).method("send"); warehouse.expects(once()).method("hasInventory") .withAnyArguments() .will(returnValue(false)); order.fill((Warehouse) warehouse.proxy()); } } 

为了在存根上使用状态validation,我需要在存根上做一些额外的方法来帮助validation。 因此,存根实现MailService,但添加了额外的>testing方法。

答案与大多数关于实践的问题一样,是“视情况而定”。

过度使用模拟会导致testing不能真正testing任何东西。 它也可以导致testing代码在被测代码中的虚拟重新实现,紧密地绑定到特定的实现。

另一方面,明智地使用模拟和存根可以导致unit testing,这些testing是非常孤立的,只testing一件事情和一件事情 – 这是一件好事。

这是关于适度。

模拟类是有意义的,所以testing可以在开发生命周期的早期编写。

即使有具体的实现可用,仍然有继续使用模拟类的倾向。 在系统的某些部分还没有build成的时候,还有一个倾向,就是在项目的早期阶段需要开发模拟类(和存根)。

一旦build立了一个系统的一部分,就有必要对它进行testing,并继续对它进行testing(对于回归)。 在这种情况下,从模拟开始是好的,但应该尽快抛弃它们以利于实施。 我看到了项目的斗争,因为不同的团队继续发展模拟的行为,而不是执行(一旦可用)。

通过testing嘲笑你假设模拟是系统的特点。 通常这包括猜测被嘲弄的组件将会做什么。 如果你有嘲笑系统的规格说明,那么你不必猜测,但是由于在施工过程中发现的实际问题,通常“竣工”系统与原始规格不符。 敏捷开发项目假设这将始终发生。

然后您开发与模拟一起工作的代码。 当事实certificate这个模拟不能真正代表真正的竣工系统的行为(例如模拟,并发问题,性能问题等中没有看到的模拟,资源和效率问题中没有看到的延迟问题)有一堆毫无价值的嘲笑testing,你现在必须维护。

我认为在开发开始时使用mock是有价值的,但是这些mock不应该有助于项目的覆盖。 最好稍后如果移除了模拟并且创build了适当的集成testing来replace它们,否则你的系统将不会得到你的模拟没有模拟的各种条件(或者相对于真实的系统模拟不正确)的testing。

所以,问题是是否使用mock,是什么时候使用它们以及什么时候删除它们的问题。