我知道我如何使用这些术语,但我想知道是否有单独测试的伪造,模拟和存根的可接受定义?你如何为你的测试定义这些?描述您可能使用每种情况的情况.
以下是我如何使用它们:
假:一个实现接口但包含固定数据而没有逻辑的类.根据实施情况,简单地返回"好"或"坏"数据.
Mock:一个实现接口的类,允许动态设置值以返回/异常从特定方法抛出,并提供检查是否已调用/未调用特定方法的功能.
存根:类似于模拟类,但它不提供验证方法是否已被调用/未调用的能力.
模拟和存根可以由模拟框架手动生成或生成.伪造的类是手工生成的.我主要使用模拟来验证我的类和依赖类之间的交互.一旦我验证了交互并且正在通过我的代码测试备用路径,我就会使用存根.我主要使用假类来抽象出数据依赖性,或者每次使用模拟/存根都太繁琐.
你可以得到一些信息:
假对象实际上有工作实现,但通常采取一些捷径,使它们不适合生产
存根提供了在测试期间进行的调用的固定答案,通常不会对测试中编程的任何内容做出任何响应.存根还可以记录有关呼叫的信息,例如记住它'发送'的消息的电子邮件网关存根,或者可能只记录它'发送'的消息.
模拟是我们在这里所讨论的:预编程的对象具有预期,形成了预期接收的调用的规范.
假:我们获得或构建一个非常轻量级的实现,其功能与SUT所依赖的组件提供的功能相同,并指示SUT使用它而不是真实的.
存根:此实现配置为响应来自SUT的调用,其中包含将执行SUT内未测试代码(请参见第X页上的生产错误)的值(或例外).使用测试存根的关键指示是由于无法控制SUT的间接输入而导致的未测试代码
模拟对象实现与SUT(受测试系统)所依赖的对象相同的接口.当我们需要进行行为验证时,我们可以使用Mock对象作为观察点,以避免因无法观察SUT上调用方法的副作用而导致出现未经测试的要求(请参阅第X页的生产错误).
我试着通过使用:Mock和Stub进行简化.当它是一个返回一个设置为测试类的值的对象时,我使用Mock.我使用Stub来模拟要测试的Interface或Abstract类.事实上,你所谓的它并不重要,它们都是未在生产中使用的类,并且被用作测试的实用程序类.
存根 - 为方法调用提供预定义答案的对象.
模拟 - 您设定期望的对象.
假 - 一种功能有限的对象(用于测试),例如虚假的Web服务.
Test Double是短柱,模拟和假货的总称.但非正式地说,你经常听到人们只是称他们为嘲笑.
令我感到惊讶的是,这个问题已经存在了很长时间,而且还没有人根据Roy Osherove的"单元测试艺术"提供答案.
在"3.1 Introducing stubs"中,将stub定义为:
存根是系统中现有依赖关系(或协作者)的可控替代.通过使用存根,您可以在不直接处理依赖关系的情况下测试代码.
并定义存根和模拟之间的区别:
关于mocks和stubs的主要问题是模拟就像存根一样,但你对模拟对象断言,而你没有断言存根.
假只是用于存根和模拟的名称.例如,当您不关心存根和模拟之间的区别时.
Osherove区分存根和模拟的方式意味着任何用作测试假的类都可以是存根或模拟.对于特定测试而言,完全取决于您在测试中编写检查的方式.
当您的测试检查被测试的类中的值时,或实际上除了伪造之外的任何地方,假的被用作存根.它只是为要使用的类提供了值,要么直接通过调用返回的值,要么间接通过引起副作用(在某些状态下)作为对它的调用.
当你的测试检查假的值时,它被用作模拟.
将FakeX类用作存根的测试示例:
const pleaseReturn5 = 5; var fake = new FakeX(pleaseReturn5); var cut = new ClassUnderTest(fake); cut.SquareIt; Assert.AreEqual(25, cut.SomeProperty);
该fake
实例用作存根,因为它Assert
根本不使用fake
.
测试类X用作模拟的测试示例:
const pleaseReturn5 = 5; var fake = new FakeX(pleaseReturn5); var cut = new ClassUnderTest(fake); cut.SquareIt; Assert.AreEqual(25, fake.SomeProperty);
在这种情况下,Assert
检查一个值fake
,使假的模拟.
现在,这些例子当然是非常人为的,但我认为这种区别很有价值.它让您了解如何测试您的内容以及测试的依赖性.
我同意Osherove的观点
从纯粹的可维护性角度来看,在我使用模拟的测试中,比不使用它们会带来更多麻烦.这是我的经历,但我总是在学习新东西.
断言假冒是你真正想要避免的,因为它会使你的测试高度依赖于一个完全没有被测试的类的实现.这意味着类的测试ActualClassUnderTest
可以开始打破,因为实现已ClassUsedAsMock
更改.这给我带来了难闻的气味.测试ActualClassUnderTest
应优选仅在ActualClassUnderTest
更改时中断.
我意识到写假冒伪劣是一种常见的做法,特别是当你是一个模仿者类型的TDD用户时.我想我对古典主义阵营中的Martin Fowler很坚定(参见Martin Fowler的"Mocks are not Stubs"),并且像Osherove一样,尽可能避免交互测试(这只能通过断言对付假冒).
为了有趣的阅读为什么你应该避免这里定义的模拟,谷歌搜索"福勒模仿古典主义者".你会发现很多意见.
正如投票最多的答案所提到的那样,Martin Fowler在Mocks Are n't Stubs中讨论了这些区别,特别是在副标题The Mocks and Stubs之间的区别,因此请务必阅读该文章。
而不是着眼于如何这些东西都是不同的,我认为这是更具启发专注于为什么这些是不同的概念。每种存在的目的都不相同。
一个假冒是一个执行,其行为“自然”,而不是“真实的”。这些都是模糊的概念,因此不同的人对使事情变得假的有不同的理解。
伪造品的一个例子是内存数据库(例如,在:memory:
商店中使用sqlite )。您永远不会将其用于生产(因为数据不会持久保存),但是它完全可以用作测试环境中的数据库。它也比“真实”数据库轻巧得多。
再举一个例子,也许您在生产中使用了某种对象存储(例如Amazon S3),但是在测试中,您可以将对象简单地保存到磁盘上的文件中。那么您的“保存到磁盘”实现将是假的。(或者您甚至可以通过使用内存文件系统来伪造“保存到磁盘”操作。)
第三个示例,想象一个提供缓存API的对象。一个实现正确接口但根本不执行任何缓存但始终返回缓存未命中的对象将是一种伪造。
伪造的目的不是影响被测系统的行为,而是简化测试的实施(通过删除不必要的或重量级的依赖项)。
一个存根的行为方式“不自然”的实现。它已预先配置(通常是通过测试设置)以响应具有特定输出的特定输入。
存根的目的是使被测系统进入特定状态。例如,如果你正在编写一个测试一些代码,与REST API交互,您可以存根出的REST API与API,它总是返回一个罐头回应,或者回应给特定错误的API请求。这样,您可以编写测试来断言系统对这些状态的反应。例如,如果API返回404错误,则测试用户获得的响应。
通常仅将存根实现为仅响应您已告诉其响应的确切交互。但是,使存根变成某些东西的关键功能是它的目的:存根就是建立测试用例。
一个模拟类似于存根,但核查中加入一个模拟的目的是为了让你的测试系统如何与依赖互动的断言。
例如,如果要编写一个将文件上传到网站的系统的测试,则可以构建一个接受文件的模拟文件,并可以使用该文件断言上传的文件是正确的。或者,在较小的规模上,通常使用对象的模拟来验证被测系统是否调用了模拟对象的特定方法。
模拟是与交互测试相关的,交互测试是一种特定的测试方法。喜欢测试系统状态而不是系统交互的人将很少使用模拟程序。
伪造品,存根和模拟品均属于测试双打的类别。测试倍数是您在测试中使用的任何对象或系统,而不是其他对象。大多数自动化软件测试都使用某种或多种测试倍数。其他一些测试双精度类型包括伪值,间谍和I / O 黑洞。
为了说明存根和模拟的用法,我还想提供一个基于Roy Osherove的" 单元测试艺术 "的例子.
想象一下,我们有一个LogAnalyzer应用程序,它具有打印日志的唯一功能.它不仅需要与Web服务通信,而且如果Web服务引发错误,LogAnalyzer必须将错误记录到不同的外部依赖项,通过电子邮件将其发送给Web服务管理员.
这是我们想要在LogAnalyzer中测试的逻辑:
if(fileName.Length<8) { try { service.LogError("Filename too short:" + fileName); } catch (Exception e) { email.SendEmail("a","subject",e.Message); } }
当Web服务抛出异常时,如何测试LogAnalyzer是否正确调用电子邮件服务?以下是我们面临的问题:
我们如何替换Web服务?
我们如何模拟Web服务中的异常,以便我们可以测试对电子邮件服务的调用?
我们如何才能知道电子邮件服务是否正确调用?
我们可以通过使用Web服务的存根来处理前两个问题.为了解决第三个问题,我们可以使用模拟对象来进行电子邮件服务.
假是一个通用术语,可用于描述存根或模拟.在我们的测试中,我们将有两个假货.一个是电子邮件服务模拟,我们将用它来验证是否已将正确的参数发送到电子邮件服务.另一个将是一个存根,我们将用它来模拟从Web服务抛出的异常.这是一个存根,因为我们不会使用Web服务假来验证测试结果,只是为了确保测试正确运行.电子邮件服务是一个模拟,因为我们会断言它被正确调用.
[TestFixture] public class LogAnalyzer2Tests { [Test] public void Analyze_WebServiceThrows_SendsEmail() { StubService stubService = new StubService(); stubService.ToThrow= new Exception("fake exception"); MockEmailService mockEmail = new MockEmailService(); LogAnalyzer2 log = new LogAnalyzer2(); log.Service = stubService log.Email=mockEmail; string tooShortFileName="abc.ext"; log.Analyze(tooShortFileName); Assert.AreEqual("a",mockEmail.To); //MOCKING USED Assert.AreEqual("fake exception",mockEmail.Body); //MOCKING USED Assert.AreEqual("subject",mockEmail.Subject); } }
您在其中声明的东西称为模拟对象,而其他仅有助于测试运行的东西都是存根。
这是使测试具有表现力的问题.如果我想让测试描述两个对象之间的关系,我会对模拟设置期望.如果我正在设置支持对象以使我了解测试中的有趣行为,那么我会返回值.
如果您熟悉Arrange-Act-Assert,那么一种解释存根和模拟对您可能有用的区别的方法是,存根属于安排部分,因为它们是用于安排输入状态的,而模拟属于断言部分,因为它们用于断言结果。
假人什么也没做。它们仅用于填充参数列表,因此不会出现未定义或空错误。它们也可以满足严格类型化语言中的类型检查器的要求,因此可以允许您编译和运行它们。