什么是嘲讽?

什么是嘲讽?                                                                                                    .

320642 次浏览

在SO网站上有很多关于嘲讽的答案,网上也有很多关于嘲讽的好帖子。你可能想从Martin Fowler的帖子mock不是stub开始看,他讨论了很多嘲笑的想法。

mock是一种特殊的技术,可以在不依赖依赖项的情况下测试代码单元。一般来说,mock与其他方法的区别在于用于替换代码依赖关系的mock对象将允许设置期望—mock对象将知道您的代码如何调用它以及如何响应。


你最初的问题提到了TypeMock,所以我把我的答案留在下面:

TypeMock是商业模仿框架的名称。

它提供了免费模拟框架(如RhinoMocks和Moq)的所有功能,以及一些更强大的选项。

你是否需要TypeMock是有很大争议的——你可以用免费的模拟库做你想做的大部分模拟,很多人争论说TypeMock提供的功能常常会让你远离封装良好的设计。

正如另一个回答所说,“TypeMocking”实际上不是一个定义的概念,但可以理解为TypeMock提供的模仿类型,使用CLR分析器在运行时拦截。net调用,提供更大的伪造对象的能力(而不是需要接口或虚拟方法之类的需求)。

我认为TypeMock隔离器模仿框架的使用应该是TypeMocking。

它是一种生成模拟以用于单元测试的工具,无需在编写代码时考虑IoC。

模拟类型的目的是切断依赖关系,以便将测试隔离到特定的单元。存根是简单的代理,而模拟是可以验证使用情况的代理。模拟框架是一种帮助您生成存根和模拟的工具。

编辑:因为最初的措辞提到了“类型嘲弄”,我的印象是这与TypeMock有关。根据我的经验,一般的说法就是“嘲笑”。请随意忽略下面的信息,特别是TypeMock。

TypeMock Isolator与大多数其他模拟框架的不同之处在于,它可以在运行中修改IL。这允许它模拟大多数其他框架无法模拟的类型和实例。为了用其他框架模拟这些类型/实例,你必须提供自己的抽象并模拟它们。

TypeMock以牺牲干净的运行时环境为代价提供了极大的灵活性。作为TypeMock实现其结果的方式的副作用,您在使用TypeMock时有时会得到非常奇怪的结果。

开场白:如果你在字典里查模拟这个名词,你会发现这个词的一个定义是仿制品:仿制品


mock主要用于单元测试。被测试的对象可能依赖于其他(复杂的)对象。为了隔离您想要测试的对象的行为,您可以用模拟真实对象行为的mock替换其他对象。如果将实际对象合并到单元测试中是不切实际的,那么这是很有用的。

简而言之,模拟是创建模拟真实对象行为的对象。


有时你可能想要区分嘲笑存根。关于这个问题可能会有一些分歧,但我对存根的定义是“最小的”。模拟对象。存根实现了足够的行为以允许被测试对象执行测试。

mock类似于存根,但测试也将验证被测试对象是否按预期调用mock。测试的一部分是验证mock是否被正确使用。

举个例子:您可以通过实现一个简单的内存结构来存储记录来存根数据库。然后,被测试对象可以将记录读写到数据库存根,以允许它执行测试。这可以测试对象的一些与数据库无关的行为,数据库存根将被包括在内,只是为了让测试运行。

如果您想验证被测试对象是否将某些特定数据写入数据库,则必须模拟数据库。然后,您的测试将包含关于写入数据库模拟的内容的断言。

Mock是以可控的方式模拟真实方法/对象的行为的方法/对象。模拟对象用于单元测试。

测试下的方法通常会调用它内部的其他外部服务或方法。这些被称为依赖关系。一旦模拟,依赖项就会按照我们定义的方式运行。

由于依赖项由mock控制,我们可以很容易地测试我们编写的方法的行为。这就是单元测试。

模拟对象的目的是什么?< / >

mock vs stub

单元测试vs功能测试

如果您的模拟涉及网络请求,另一种替代方法是使用真正的测试服务器。您可以使用服务为您的测试生成请求和响应。

其他答案解释了什么是嘲笑。让我用不同的例子来引导你。相信我,这其实比你想象的要简单得多。

它是原始类的一个实例。它还注入了其他数据,这样你就可以避免测试注入的部分,而只测试类/函数的实现细节

简单的例子:

class Foo {
func add (num1: Int, num2: Int) -> Int { // Line A
return num1 + num2 // Line B
}
}


let unit = Foo() // unit under test
assertEqual(unit.add(1,5),6)

如您所见,我没有测试LineA,即我没有验证输入参数。我没有验证,看看num1, num2是一个整数。我对此没有异议。

我只是测试看看LineB(我的实现)给定的模拟值15是我所期望的。

显然,在现实世界中,这可能会变得复杂得多。参数可以是一个自定义对象,比如Person、Address,或者实现细节可以不止一个+。但是测试的逻辑是一样的。

非编码的例子:

假设您正在构建一台机器,用于识别机场安检电子设备的类型和品牌名称。这台机器通过摄像头处理它看到的东西来做到这一点。

现在你的经理走进来,让你对它进行单元测试。

作为开发人员,你可以带1000个实物,比如MacBook pro、谷歌Nexus、香蕉、iPad等,然后测试看看是否一切正常。

但你也可以使用嘲笑对象,比如一个外观一模一样的MacBook pro(没有真正的内部部件),或者在它前面放一个塑料香蕉。你可以省下1000台真正的笔记本电脑和腐烂的香蕉。

关键是你不是在测试香蕉是假的还是假的。也不测试笔记本电脑的真假。你所做的只是测试,如果你的机器看到香蕉,它会说not an electronic device,而对于MacBook Pro,它会说Laptop, Apple。对于机器来说,它的检测结果应该是假的/模拟的电子产品和真正的电子产品。如果你的机器还考虑了笔记本电脑(x射线扫描)或香蕉的内部结构,那么你的模拟内部结构也需要看起来相同。但你也可以使用一台不能用的MacBook。

你的机器已经测试过设备是否可以开机,那么你就需要真正的设备。

上面提到的逻辑也适用于实际代码的单元测试。这是一个函数,对于从真正的输入(和交互)或在单元测试期间注入的嘲笑值中获得的实际值应该是相同的。就像你不用使用真正的香蕉或MacBook一样,通过单元测试(和模拟),你不必做一些导致服务器返回500、403、200等状态代码的事情(强制服务器触发500只在服务器关闭时,而200是在服务器启动时。

如果在切换服务器之间需要持续等待10秒,那么运行100个网络测试就会变得很困难)。因此,您可以使用状态代码500、200、403等注入/模拟响应,并使用注入/模拟值测试您的单元/函数。

请注意:

有时你不能正确地模拟实际对象。或者你不会嘲笑每一种可能性。例如,你的假笔记本电脑是黑色的,你的机器可以准确地使用它们,但它不能准确地使用白色假笔记本电脑。后来当你把这台机器交付给客户时,他们抱怨它不能一直工作。你会收到随机报告说它不起作用。你花了3个月的时间来弄清楚假笔记本电脑的颜色需要更多样,这样你就可以适当地测试你的模块。

对于一个真实的编码示例,返回图像数据的状态代码200和不返回图像数据的状态代码200的实现可能是不同的。出于这个原因,最好使用一个提供代码覆盖率的IDE,例如,下图显示你的单元测试从来没有经过标记为棕色(的)的行。

enter image description here

图像来源 .

现实世界编码示例:

假设你正在编写一个iOS应用程序,并且有网络呼叫。您的工作是测试你的应用程序。测试/识别网络呼叫是否按预期工作不是您的责任。测试是另一方(服务器团队)的责任。你必须删除这个(网络)依赖,但继续测试所有的代码,工作周围

网络调用可以使用JSON响应返回不同的状态代码404、500、200、303等。

你的应用应该为所有工作(如果出现错误,你的应用应该抛出它预期的错误)。你用mock做的是你创建“想象的类似于真实的”网络响应(像一个JSON文件的200代码),并测试你的代码没有“做出真实的网络调用并等待你的网络响应”。你手动硬编码/返回所有类型的网络响应,看看你的应用程序是否像你期望的那样工作。(你从来没有假设/测试一个200与不正确的数据,因为这不是你的责任,你的责任是测试你的应用程序与正确的200,或在400,500的情况下,你测试你的应用程序是否抛出正确的错误)

这种虚构的类似于真实的创造被称为嘲弄。

为了做到这一点,不能使用原始代码(原始代码没有预先插入的响应,对吗?)你必须添加一些东西,注入/插入那些通常不需要的虚假数据(或你的类的一部分)。

因此,您创建了原始类的实例,并添加所需的任何东西(这里是网络HTTPResponse,数据或在失败的情况下,您传递正确的errorString, HTTPResponse),然后测试嘲笑类。

长话短说,mock是针对简化限制您正在测试的内容,并让您提供类所依赖的内容。在这个例子中,你避免测试 网络呼叫他们自己,而不是测验,不管你的应用程序是否像你期望的那样使用注入的输出/响应——通过嘲笑

不用说,您将分别测试每个网络响应。


现在,我脑海中一直有一个问题:我的api的契约/端点和JSON响应会不断更新。我如何编写单元测试来考虑这一点呢?

更详细地说:让我们说模型需要一个名为username的键/字段。你测试这个,你的测试通过了。 2周后,后端将密钥的名称更改为id。你的测试还是通过了对吧?或不呢?< / p >

后端开发人员是否有责任更新模拟?他们提供更新的模拟应该是我们协议的一部分吗?

上述问题的答案是:单元测试+作为客户端开发人员的开发过程应该/将捕获过时的模拟响应。如果你问我怎么做?答案是:

如果不使用更新的api,我们实际的应用程序将失败(或没有失败,但没有所需的行为)……因此,如果失败了……我们将改变我们的开发代码。这再次导致我们的测试失败....我们必须纠正它。(实际上,如果我们要正确地执行TDD过程,我们就不应该编写任何关于该字段的代码,除非我们为它编写测试……看着它失败,然后去为它写实际的开发代码。)

这意味着后台不必说:“嘿,我们更新了模拟”……它最终发生在你的代码开发/调试中。ّBecause这都是开发过程的一部分!虽然如果后端提供模拟响应为您那么它更容易。

我的全部观点是(如果你不能自动获得更新的模拟API响应)可能需要人工交互,即JSONs的手册更新,并举行简短的会议以确保它们的值是最新的,这将成为你的流程的一部分

本节的撰写多亏了CocoaHead聚会组的一次松散讨论

困惑:

我花了一段时间才把“类的单元测试”和“类的存根/mock”搞混。 例如,在我们的代码库中,我们有:

  • 类设备
  • 类DeviceTests
  • 类MockDevice
  • 类DeviceManager

  • class Device是实际的类本身。
  • class DeviceTests是我们为Device类编写单元测试的地方
  • class MockDeviceDevice模拟类。我们使用它只是为了测试。例如,如果我们的DeviceManager需要获得单元测试,那么我们需要Device类的虚拟/模拟实例。MockDevice可以用来满足虚拟/模拟实例的需要。

tldr您使用模拟类/对象来测试其他对象。您不使用模拟对象来测试它们本身。


仅适用于iOS开发者:

Natasha Muraschev的实用协议导向演讲就是一个很好的例子。直接跳到18:30分,尽管幻灯片可能与实际视频不同步🤷‍♂️

我非常喜欢这段录音:

因为这是测试…我们要确保get函数 从Gettable被调用,因为它可以返回和函数 理论上可以从的任意位置分配一个食物数组。我们 需要确保它被称为;

模拟是为测试生成模拟真实对象行为的伪对象