嘲弄的目的

时间:2009-06-16 15:30:15

标签: unit-testing tdd mocking moq testdrivendesign

嘲笑的目的是什么?

我一直在关注一些使用NUnit进行测试的ASP.NET MVC教程和使用Moq进行模拟的教程。我虽然对它的嘲弄部分有点不清楚。

8 个答案:

答案 0 :(得分:16)

模拟的目的是将被测试的类与其他类隔离开来。

这在课程中很有用:

  • 连接到外部资源(FileSystem,DB,network ...)
  • 设置昂贵,或者尚未提供(正在开发的硬件)
  • 减慢单元测试的执行速度
  • 具有非确定性行为
  • 拥有(或是)用户界面

它还可以更容易地测试错误条件,因为您构建模拟对象以便返回并返回错误,抛出异常......

模拟可以记录它的调用方式(函数调用顺序,参数),这可以通过测试来验证。编辑:例如: 您正在测试的方法会发送消息,例如IPC。模拟对象的方法可以记录调用它的次数, 他收到的参数(即要发送的消息)。然后测试可以询问模拟对象并断言发送的消息数量,消息的内容...... 同样,模拟对象可以记录在日志字符串中调用的方法,测试可以检索该字符串并在其上进行断言。

不要滥用模拟对象:测试行为而不是实现,否则单元测试将与代码紧密耦合,并且脆弱(在重构时中断)。

模拟可以手动编码,也可以由mocking framework生成。

答案 1 :(得分:7)

Mocking允许您将测试中的类与其依赖项隔离开来。通常,您为要测试的类的每个依赖项创建一个模拟,并设置模拟以返回预期值。然后,您将模拟提供给正在测试的类,而不是您所测试的类所依赖的类的实际副本。然后,您可以使用模拟框架检查是否对模拟对象进行了预期的调用,以确保您的测试类正常运行。

答案 2 :(得分:4)

它旨在通过群组收集个人实例的乐趣。在不规则的物体聚会中经常使用。

答案 3 :(得分:4)

虽然模拟通常被理解为允许隔离被测试的类,但这不是模拟的主要观点(存根对此更好)。相反,我们需要看一下当一个对象被告知要做三件事之一时会发生什么......

  1. 直接输出 - 方法调用的结果
  2. 内部更改 - 在方法调用期间对类的更改
  3. 间接输出 - 测试中的代码调用不同的类
  4. 基于状态的测试完全是关于#1和#2。 #1通过查看该方法为您提供的结果。 #2通过访问对象内部状态。

    这给我们留下了#3,我们可以采取两种途径。第一个是使用Mock,第二个是使用Test Spy。主要区别在于,在Mock中,您在执行测试代码之前创建期望,然后让模拟验证它们,然后使用Test Spy执行测试中的代码,然后询问测试间谍是否发生了某些操作。 / p>

    所以总结一下......当你考虑测试一个类的作用时,如果你需要测试间接输出(也就是调用另一个类),这就是Mocking发挥作用的地方。

答案 4 :(得分:3)

我也是嘲笑的新人,但我会对此嗤之以鼻。根据我的经验,嘲笑有两个主要好处:

  • 您可以在实际编写实现之前开始使用对象。您可以定义一个接口,并使用模拟在单元测试甚至代码中使用接口。
  • Mocking允许您在单元测试中隔离测试对象。使用模拟对象,您可以完全控制被测对象与之交互的任何对象,从而从测试中删除外部依赖项。

答案 5 :(得分:3)

“模拟”是一个严重超载的测试和术语。 TDD圈子。请参阅Martin Fowler的文章Mocks Aren't Stubs。一个“正确”的模拟器知道它应该接收什么值,并让你知道什么时候它没有达到预期的目的;这允许您进行交互测试而不是状态测试 - 您验证被测试的类是否以正确的顺序将正确的消息传递给其协作者。交互测试与传统的状态测试完全不同,可能很难理解。请记住,交互测试是模拟的重点可能使它们更容易理解。

答案 6 :(得分:0)

另一个答案:

  • Stub =假对象能够在没有整个真实背景的情况下运行测试

  • 模拟=假对象记录组件的交互并验证这些交互

你可以在一个测试中有几个存根,但只有一个模拟器,因为如果你有多个模拟,你肯定会测试多个功能(并且它违背了测试一项原则的目的)。

为了超越基本要求,Mocks比测试的传统状态验证更多的行为验证,在您对其进行操作后检查组件的状态(安排,行动,断言与模拟更安排,行动,验证):

状态验证Assert.AreEqual(valueExpected,mycomponent.Property); 行为验证:myMock.WasCalled(MyMethod);

答案 7 :(得分:0)

通过Bert F

进行模拟的实时示例

单元测试

想象一下这个系统的单元测试:

cook <- waiter <- customer

通常很容易设想测试cook

等低级组件
cook <- test driver

测试驾驶员只需订购不同的菜肴,并确认厨师为每个订单返回正确的菜肴。

更难以测试利用其他组件行为的中间组件,如服务员。一个天真的测试人员可能会像我们测试烹饪组件一样测试服务员组件:

cook <- waiter <- test driver

测试司机会订购不同的菜肴,并确保服务员返回正确的菜肴。不幸的是,这意味着服务员组件的这种测试可能取决于烹饪组件的正确行为。如果烹饪组件具有任何测试不友好的特征,例如非确定性行为(菜单包括厨师作为菜肴的惊喜),这种依赖性更加严重,许多依赖性(厨师不会做他的整个烹饪工作人员),或许多资源(有些菜需要昂贵的食材或需要一个小时的时间来烹饪)。

由于这是服务员测试,理想情况下,我们只想测试服务员,而不是厨师。具体来说,我们希望确保服务员正确地将客户的订单传达给厨师,并正确地将厨师的食品送到客户手中。

单元测试是指独立测试单元,因此更好的方法是使用Fowler calls test doubles (dummies, stubs, fakes, mocks)隔离被测组件(服务员)。

    -----------------------
   |                       |
   v                       |
test cook <- waiter <- test driver

在这里,测试厨师是&#34;在cahoots&#34;与测试驱动程序。理想情况下,被测系统的设计使得测试厨师可以轻松替换(injected)以与服务员一起工作而无需更改生产代码(例如,无需更改服务员代码)。

模拟对象

现在,测试厨师(测试双人)可以采用不同的方式实现:

  • 假厨师 - 假装用冷冻晚餐和微波炉做饭的人,
  • 一个存根厨师 - 一个热狗供应商,无论你订购什么,总是给你热狗,或
  • 一个模拟厨师 - 一个卧底警察跟随一个假装在刺痛行动中做饭的剧本。

请参阅Fowler's article for the more specifics about fakes vs stubs vs mocks vs dummies,但就目前而言,让我们专注于模拟厨师。

    -----------------------
   |                       |
   v                       |
mock cook <- waiter <- test driver

服务员组件的单元测试的一个重要部分集中在服务员如何与烹饪组件交互。基于模拟的方法侧重于完全指定正确的交互是什么,并检测何时出错。

模拟对象事先知道在测试期间应该发生什么(例如,将调用哪个方法调用等),并且模拟对象知道它应该如何反应(例如,提供什么返回值) 。模拟将指示实际发生的事情是否与应该发生的事情不同。可以针对每个测试用例的预期行为对自定义模拟对象进行编码,但是模拟框架努力允许在测试用例中直接明确地指示这样的行为规范。

围绕基于模拟的测试的对话可能如下所示:

  

测试驱动程序模拟厨师期待热狗订单并给他这个假热狗作为回应

     

测试驱动程序(冒充客户)服务员我想要一个热狗
  服务员模拟厨师 1热狗
  模拟厨师服务员订购:1条准备好的热狗(给服务员提供假热狗)
  服务员测试驱动程序这是你的热狗(给测试驱动程序的假热狗)

     

测试驱动程序:TEST SUCCEEDED!

但由于我们的服务员很新,所以可能会发生这种情况:

  

测试驱动程序模拟厨师期待热狗订单并给他这个假热狗作为回应

     

测试驱动程序(冒充客户)服务员我想要一个热狗
  服务员模拟厨师 1汉堡包
  模拟厨师停止测试:我被告知期待热狗订单!

     

测试驱动程序注意到问题:测试失败! - 服务员改变了订单

  

测试驱动程序模拟厨师期待热狗订单并给他这个假热狗作为回应

     

测试驱动程序(冒充客户)服务员我想要一个热狗
  服务员模拟厨师 1热狗
  模拟厨师服务员订购:1条准备好的热狗(给服务员提供假热狗)
  服务员测试驱动程序这是您的炸薯条(从其他订单中提供炸薯条到测试驱动程序)

     

测试驱动程序注意到意外炸薯条:TEST FAILED!服务员给了错误的菜肴

可能很难清楚地看到模拟对象和存根之间的区别而没有基于存根的对比示例,但是这个答案已经太久了: - )

另请注意,这是一个非常简单的示例,并且模拟框架允许从组件的一些非常复杂的预期行为规范来支持全面测试。有关模拟对象和模拟框架的大量资料可以获得更多信息。