为什么我们需要模拟框架?

我使用过编写过NUnit测试的代码。 但是,我从未使用过模拟框架。 这些是什么? 我理解dependency injection以及它如何帮助提高可测试性。 我的意思是所有依赖项都可以在unit testing时进行模拟。 但是,为什么我们需要模拟框架呢? 我们不能简单地创建模拟对象并提供依赖关系。 我在这里错过了什么吗? 谢谢。

  • 它使模拟更容易
  • 它们通常允许您表达可测试的断言,这些断言引用对象之间的交互。

这里有一个例子:

var extension = MockRepository .GenerateMock>(); var ctx = new StandardContext(); ctx.AddExtension(extension); extension.AssertWasCalled( e=>e.Attach(null), o=>o.Constraints(Is.Equal(ctx))); 

您可以看到我明确地测试了IContextExtension的Attach方法被调用,并且输入参数是所述上下文对象。 如果没有发生,它会使我的测试失败。

您可以手动创建模拟对象,并在使用dependency injection框架进行测试期间使用它们……但是让模拟框架为您生成模拟对象可以节省时间。

与往常一样,如果使用框架增加了太多的复杂性,那么就不要使用它。

有时,在使用第三方库,甚至使用.NET框架的某些方面时,在某些情况下编写测试非常困难 – 例如,HttpContext或Sharepoint对象。 为那些创建模拟对象会变得非常麻烦,因此模拟框架会处理基础知识,因此我们可以将时间花在专注于使应用程序独特的内容上。

使用模拟框架可以提供模拟的轻量级和简单的解决方案,而不是为要模拟的每个对象实际创建模拟对象。

例如,模拟框架对于执行诸如validation调用(甚至调用的次数)之类的操作特别有用。 制作自己的模拟对象来检查这样的行为(虽然模拟行为本身就是一个主题)是单调乏味的,而且是另一个引入错误的地方。

查看Rhino Mocks,了解一个模拟框架有多强大的例子。

模拟对象取代了代码需要访问的任何大型/复杂/外部对象才能运行。

它们有益于以下几个原因:

  • 您的测试旨在快速轻松地运行。 如果您的代码依赖于数据库连接,那么您需要运行完全配置和填充的数据库才能运行测试。 这可能会很烦人,所以你创建了一个替换 – 一个“模拟” – 只是模拟数据库的数据库连接对象。

  • 您可以精确控制Mock对象的输出,因此可以将它们用作测试的可控数据源。

  • 您可以创建真实对象之前创建模拟以优化其界面。 这在测试驱动开发中很有用。

使用模拟库的唯一原因是它使模拟更容易。

当然,你可以在没有库的情况下完成所有这一切,如果它很简单就可以了,但是一旦它们开始变得复杂,库就会容易得多。

从排序算法的角度考虑这一点,确定任何人都可以写一个,但为什么呢? 如果代码已经存在并且很容易调用…为什么不使用它?

你当然可以手动模拟你的依赖项,但是使用框架需要花费大量繁琐的工作。 通常可用的断言使它值得学习。

模拟框架允许您从该代码的依赖项中隔离您希望测试的代码单元。 它们还允许您在测试环境中模拟代码依赖关系的各种行为,否则可能难以设置或重现。

例如,如果我有一个包含我希望测试的业务规则和逻辑的A类,但是这个A类依赖于数据访问类,其他业务类,甚至是u / i类等,这些其他类可以被模拟以某种方式执行(或者在松散的模拟行为的情况下根本不执行),以基于这些其他类可以想象在生产环境中行为的可想象的方式来测试类A中的逻辑。

为了给出更深层次的示例,假设您的类A在数据访问类上调用方法,例如

 public bool IsOrderOnHold(int orderNumber) {} 

然后可以将该数据访问类的模拟设置为每次返回true或每次都返回false,以测试A类如何响应这种情况。

我声称你没有。 编写测试双打并不是10次中的9次。在大多数情况下,只需要让resharper为您实现一个接口,然后您只需添加此双精度所需的次要细节(因为您没有做一堆逻辑并创建这些错综复杂的超级测试双打,对吧?对吗?)

你可能会问,“但为什么我希望我的测试项目充满了一堆测试双打”。 那么你不应该。 DRY原则也适用于测试。 创建可重复使用且具有描述性名称的GOOD测试双精度数。 这使您的测试更具可读性。

它更难做的一件事是过度使用测试双打。 我倾向于同意Roy Osherove和Uncle Bob,你真的不想创建一个经常使用一些特殊配置的模拟对象。 这本身就是一种设计气味。 使用一个框架就可以很容易地在几乎每个测试中使用复杂逻辑的测试双精度,最后你发现你没有真正测试过你的生产代码,你只是测试了那个令人难以置信的frankenstein的monsteresque混乱的模拟包含嘲笑包含更多的嘲笑。 如果你写自己的双打,你永远不会“意外地”这样做。

当然,有人会指出,有时你“有”使用框架,不这样做是明显的愚蠢。 当然,有类似的情况。 但你可能没有那种情况。 大多数人没有,只有一小部分代码,或者代码本身非常糟糕。

我建议任何人(特别是初学者)远离框架并学习如何在没有框架的情况下顺利通过,然后当他们觉得他们真的需要时他们可以使用他们认为最合适的框架,但到那时这将是一个明智的决定,他们将不太可能滥用框架来创建错误的代码。

好的模拟框架使我的生活更轻松,更乏味,所以我可以花时间实际编写代码。 以Mockito为例(在Java世界中)

  //mock creation List mockedList = mock(List.class); //using mock object mockedList.add("one"); mockedList.clear(); //verification verify(mockedList).add("one"); verify(mockedList).clear(); //stubbing using built-in anyInt() argument matcher when(mockedList.get(anyInt())).thenReturn("element"); //stubbing using hamcrest (let's say isValid() returns your own hamcrest matcher): when(mockedList.contains(argThat(isValid()))).thenReturn("element"); //following prints "element" System.out.println(mockedList.get(999)); 

虽然这是一个人为的例子,如果你用List.class替换List.class ,那么拥有一个List.class框架的价值就变得明显了。 你可以写自己的或不做,但为什么你想要走那条路。

我首先想知道为什么我需要一个模拟框架,当我比较一组unit testing时手动编写测试双打(每个测试需要稍微不同的行为,所以我为每个测试创建一个基本假类型的子类)使用类似的东西RhinoMocks或Moq也做同样的工作。

简单地说,使用框架来生成我需要的所有假对象要快得多,而不是手工编写(和调试)我自己的假货。