-
05-07-2019 - |
题
嘲讽的目的是什么?
我一直在关注一些 ASP.NET MVC 教程,这些教程使用 NUnit 进行测试,使用 Moq 进行模拟。不过我对它的嘲笑部分有点不清楚。
解决方案
嘲笑的目的是 隔离正在测试的类 来自其他班级。
当课程出现以下情况时,这会很有帮助:
- 连接到外部资源(文件系统、数据库、网络...)
- 设置成本昂贵,或者尚未可用(硬件正在开发中)
- 减慢单元测试的执行速度
- 具有非确定性行为
- 具有(或是)用户界面
它还可以更轻松地测试错误条件,因为您构建模拟对象以便它返回并出错,引发异常......
模拟可以记录它是如何被调用的(函数调用顺序、参数),并且可以通过测试来验证。编辑:例如:您正在测试的方法发送消息,例如 IPC。mock 对象的方法可以记录它被调用了多少次, 他收到的参数(即要发送的消息)。然后测试可以询问模拟对象并断言发送的消息数量、消息内容......同样,模拟对象可以记录在日志字符串中调用的方法,并且测试可以检索该字符串并对其进行断言。
不要滥用模拟对象: :测试行为而不是实现,否则单元测试将与代码过于紧密地耦合,并且很脆弱(在重构时中断)。
模拟可以手动编码,也可以由 模拟框架.
其他提示
Mocking允许您将测试中的类与其依赖项隔离开来。通常,您为要测试的类的每个依赖项创建一个模拟,并设置模拟以返回预期值。然后,您将模拟提供给正在测试的类,而不是您所测试的类所依赖的类的实际副本。然后,您可以使用模拟框架检查是否对模拟对象进行了预期的调用,以确保您的测试类正常运行。
它旨在通过群组收集个人实例的乐趣。在不规则的物体聚会中经常使用。
虽然模拟通常被理解为允许隔离被测试的类,但这不是模拟的主要观点(存根对此更好)。相反,我们需要看一下当一个对象被告知要做三件事之一时会发生什么......
- 直接输出 - 方法调用的结果
- 内部更改 - 在方法调用期间对类的更改
- 间接输出 - 测试中的代码调用不同的类 醇>
基于状态的测试完全是关于#1和#2。 #1通过查看该方法为您提供的结果。 #2通过访问对象内部状态。
这给我们留下了#3,我们可以采取两种途径。第一个是使用Mock,第二个是使用Test Spy。主要区别在于,在Mock中,您在执行测试代码之前创建期望值,然后让模拟验证它们,然后使用Test Spy执行测试中的代码,然后询问测试间谍是否发生了某些操作。 / p>
所以总结一下......当你考虑测试一个类的作用时,如果你需要测试间接输出(也就是调用另一个类),这就是Mocking发挥作用的地方。
我也是嘲笑新手,但我会尝试一下。根据我的经验,嘲笑有两个主要好处:
- 您可以在实际编写实现之前开始使用对象。您可以定义一个接口并使用模拟来在单元测试甚至代码中使用该接口。
- 模拟允许您在单元测试中隔离被测对象。使用模拟对象,您可以完全控制与测试对象交互的任何对象,从而从测试中删除外部依赖项。
"模拟"在测试和测试中是一个严重超载的术语TDD圈子。请参阅Martin Fowler的文章 Mocks Are Not Stubs 。 “适当的” mock知道它应该接收什么值,并让你知道什么时候它没有达到预期的目的;这允许您进行交互测试而不是状态测试 - 您验证被测试的类是否以正确的顺序将正确的消息传递给其协作者。交互测试与传统的状态测试完全不同,可能很难理解。请记住,交互测试是模拟的重点可能使它们更容易理解。
伟大的实时嘲笑示例 伯特·F
单元测试
想象一下该系统的单元测试:
cook <- waiter <- customer
通常很容易设想测试一个低级组件,例如 cook
:
cook <- test driver
测试驾驶员只需点不同的菜肴,并验证厨师为每个订单返回正确的菜肴。
测试利用其他组件行为的中间组件(例如服务员)更困难。天真的测试人员可能会像我们测试 Cook 组件一样测试 waiter 组件:
cook <- waiter <- test driver
测试司机会点不同的菜肴,并确保服务员返回正确的菜肴。不幸的是,这意味着 waiter 组件的测试可能依赖于 Cook 组件的正确行为。如果厨师组件具有任何不利于测试的特征,例如非确定性行为(菜单包括厨师的惊喜作为一道菜)、大量依赖性(厨师不会在没有整个员工的情况下做饭)或大量依赖项,这种依赖性会更糟。资源(有些菜肴需要昂贵的食材或需要一个小时才能烹饪)。
由于这是服务员测试,理想情况下,我们只想测试服务员,而不是厨师。具体来说,我们要确保服务员正确地将顾客的点菜传达给厨师,并将厨师的食物正确地送到顾客手中。
单元测试意味着独立测试单元,因此更好的方法是使用以下内容隔离被测组件(服务员) 福勒称测试替身(假人、存根、假货、模拟).
-----------------------
| |
v |
test cook <- waiter <- test driver
在这里,测试厨师与测试驾驶员“勾结”。理想情况下,被测系统的设计使得测试厨师可以轻松替换(注入)与服务员一起工作而不更改生产代码(例如无需更改服务员代码)。
模拟对象
现在,测试厨师(测试替身)可以通过不同的方式实现:
- 假厨师 - 使用冷冻食品和微波炉冒充厨师的人,
- 一个小厨师——一个热狗供应商,无论你点什么,总是给你热狗,或者
- 模拟厨师 - 一名卧底警察按照剧本假装自己是一名厨师,参与一次诱捕行动。
看 福勒的文章,了解有关假货、存根、模拟物和假人的更多细节, ,但现在让我们关注模拟厨师。
-----------------------
| |
v |
mock cook <- waiter <- test driver
waiter 组件单元测试的很大一部分集中于 waiter 如何与 Cook 组件交互。基于模拟的方法侧重于完全指定正确的交互是什么并检测何时出错。
模拟对象提前知道测试期间应该发生什么(例如将调用哪个方法调用等)并且模拟对象知道它应该如何反应(例如提供什么返回值)。模拟将表明实际发生的情况是否与应该发生的情况不同。可以为每个测试用例的预期行为编码自定义模拟对象,但是模拟框架致力于允许在测试用例中直接清楚且轻松地指示此类行为规范。
围绕基于模拟的测试的对话可能如下所示:
试车手 到 模拟厨师: 期待一份热狗订单并给他这个虚拟热狗作为回应
试车手 (冒充顾客) 服务员: 我想要一个热狗
服务员 到 模拟厨师: 请1个热狗
模拟厨师 到 服务员: 订购:1 个热狗准备好(给服务员虚拟热狗)
服务员 到 试车手: 这是你的热狗(给测试司机虚拟热狗)试车手: :测试成功!
但由于我们的服务员是新来的,所以可能会发生以下情况:
试车手 到 模拟厨师: 期待一份热狗订单并给他这个虚拟热狗作为回应
试车手 (冒充顾客) 服务员: 我想要一个热狗
服务员 到 模拟厨师: 请 1 个汉堡
模拟厨师 停止测试: 有人告诉我要等一份热狗订单!试车手 注意到问题:测试失败!- 服务员改变了订单
或者
试车手 到 模拟厨师: 期待一份热狗订单并给他这个虚拟热狗作为回应
试车手 (冒充顾客) 服务员: 我想要一个热狗
服务员 到 模拟厨师: 请1个热狗
模拟厨师 到 服务员: 订购:1 个热狗准备好(给服务员虚拟热狗)
服务员 到 试车手: 这是你的炸薯条(将其他订单的炸薯条提供给测试司机)试车手 注意到意想不到的炸薯条:测试失败!服务员退错了菜
如果没有基于存根的对比示例,可能很难清楚地看到模拟对象和存根之间的区别,但这个答案已经太长了:-)
另请注意,这是一个非常简单的示例,模拟框架允许对组件的预期行为进行一些相当复杂的规范,以支持全面的测试。有大量关于模拟对象和模拟框架的材料以获取更多信息。
另一个答案:
Stub = 假对象,以便能够在没有整个真实上下文的情况下运行测试
Mock = 假对象来记录组件的交互并验证这些交互
在一项测试中可以有多个存根,但只能有一个模拟,因为如果您有多个模拟,那么您肯定会测试多个功能(并且它违背了测试一件事原则的目的)。
为了超越基础知识,Mock 比传统的测试状态验证更具行为验证,在传统的状态验证中,您在对其执行操作后检查组件的状态(使用 Mock 进行排列、操作、断言,它更像是排列、操作、验证):
状态验证 Assert.AreEqual(valueExpected,mycomponent.Property);行为验证:myMock.WasCalled(MyMethod);