伪装,嘲弄和剔除有什么区别?
我知道我是如何使用这些术语的,但是我想知道是否有可接受的用于unit testing的伪造 , 嘲弄和剔除的定义? 你如何定义这些testing? 描述你可能使用的情况。
以下是我如何使用它们:
Fake :实现接口但包含固定数据和无逻辑的类。 根据实施情况简单地返回“好”或“坏”数据。
模拟(Mock) :一个实现了接口的类,允许dynamic设置值的返回/exception,从特定方法抛出,并提供检查特定方法是否被调用/未调用的能力。
存根 :就像一个模拟类,除了它没有提供validation方法被调用/不被调用的能力。
嘲笑和存根(stub)可以由手工生成或由嘲笑框架生成。 假类是手工生成的。 我主要使用mock来validation我的类和相关类之间的交互。 一旦我validation了交互,并使用我的代码testing替代path,我使用了存根(stub)。 我主要使用假类来抽象出数据的依赖关系,或者当模拟/存根(stub)过于繁琐而无法每次设置时。
你可以得到一些信息:
从马丁福勒关于模拟和存根
虚假的对象实际上有工作的实现,但通常采取一些捷径,使他们不适合生产
存根提供了在testing过程中进行调用的jar装答案,通常对testing以外的任何事情都没有响应。 存根还可以logging关于呼叫的信息,例如logging其“发送”消息的电子邮件网关存根,或者可能仅logging“发送”的消息的数量。
嘲笑是我们在这里谈论的东西:预先编程有预期的对象,这些预期构成了预期接收的呼叫的规格。
从xunitpattern :
Fake :我们获取或构build一个与SUT依赖的组件提供的相同function的轻量级实现,并指示SUT使用它而不是真实的。
存根(stub) :这个实现被configuration为响应来自SUT的调用,其中的值(或例外)将在SUT中执行未经testing的代码(参见第X页的生产错误)。 使用testing桩的一个关键指标是由于无法控制SUT的间接input而导致的未testing代码
Mock对象 ,实现与SUT(被测系统)所依赖的对象相同的接口。 当我们需要进行行为validation以避免由于无法观察SUT上调用方法的副作用而导致未经testing的要求(参见第X页的生产错误)时,我们可以使用模拟对象作为观察点。
亲自
我尝试通过使用:模拟和存根(stub)来简化。 当它是一个对象返回一个设置为被testing类的值时,我使用Mock。 我使用存根来模仿要testing的接口或抽象类。 实际上,你所说的并不重要,它们都是不在生产中使用的类,并且被用作testing的工具类。
存根(Stub) – 一个为方法调用提供预定义答案的对象。
模拟 – 您设定期望的对象。
假 – 一个function有限的对象(用于testing),例如假的Web服务。
testing双是存根,嘲笑和假货的总称。 但非正式的,你会经常听到人们简单地称他们为嘲笑。
令我感到惊讶的是,这个问题已经存在了很长时间,还没有人提供罗伊·奥谢罗夫(Roy Osherove)的“unit testing的艺术”的答案。
在“3.1引言存根”中定义了一个存根:
存根是系统中现有依赖关系(或协作者)的可控替代品。 通过使用存根,您可以testing您的代码而不直接处理依赖关系。
并定义存根和嘲讽之间的区别为:
关于嘲笑与存根的主要问题是嘲笑就像存根,但是你对这个嘲讽的对象断言,而你没有断言存根。
假只是用于存根和嘲讽的名字。 例如,当你不关心存根和嘲讽之间的区别时。
Osherove区分存根和嘲讽的方式意味着用作testing的任何类别都可以是存根或模拟。 它是一个具体的testing完全取决于你如何在你的testing书写检查。
- 当你的testing在被testing的类中检查值,或者实际上除了假的地方以外的任何地方,假的被用作存根。 它只是提供被testing类的值,可以直接通过调用返回的值,也可以通过调用副作用(某些状态)间接调用。
- 当你的testing检查假冒的价值,它被用来作为一个模拟。
类FakeX用作存根的testing示例:
const pleaseReturn5 = 5; var fake = new FakeX(pleaseReturn5); var cut = new ClassUnderTest(fake); cut.SquareIt; Assert.AreEqual(25, cut.SomeProperty);
fake
实例被用作存根,因为Assert
根本不使用fake
。
将testing类别X用作模拟的testing示例:
const pleaseReturn5 = 5; var fake = new FakeX(pleaseReturn5); var cut = new ClassUnderTest(fake); cut.SquareIt; Assert.AreEqual(25, fake.SomeProperty);
在这种情况下, Assert
检查一个fake
的价值,使这个假的模拟。
现在,这些例子当然是非常有意思的,但是我看到这个区别有很大的价值。 它让你意识到你如何testing你的东西以及testing的依赖关系。
我同意Osherove的那个
从纯粹的可维护性的angular度来看,在我的testing中使用mock会比不使用它们造成更多的麻烦。 这是我的经验,但我总是在学习新的东西。
主张反对假冒是你真正想要避免的,因为它使得你的testing高度依赖于不是被testing类的实现。 这意味着类ActualClassUnderTest
的testing可以开始破坏,因为ClassUsedAsMock
的实现ClassUsedAsMock
更改。 这给我一个恶臭。 ActualClassUnderTest
testing应该最好只当ActualClassUnderTest
改变时才会中断。
我意识到写出反对假冒的做法是很常见的做法,特别是当你是一个模仿者的TDD用户时。 我想我是和马丁·福勒(Martin Fowler)在古典主义阵营(参见Martin Fowler的“ Mocks is not Stubs” )中坚定不移的,并且像Osherove尽可能地避免了交互testing(这只能通过对抗假冒来完成)。
为了有趣的阅读,为什么你应该避免这里定义的嘲笑,谷歌“fowler嘲弄古典主义”。 你会发现过多的意见。
这是一个使testingexpression的问题。 如果我想要testing来描述两个对象之间的关系,我可以在Mock上设置期望值。 如果我设置了一个支持对象来让我在testing中感兴趣的行为,我存根返回值。
为了说明存根和嘲讽的用法,我还想列举一个基于Roy Osherove的“ unit testing的艺术 ”的例子。
想象一下,我们有一个LogAnalyzer应用程序,它具有打印日志的唯一function。 它不仅需要与Web服务对话,而且如果Web服务引发错误,LogAnalyzer必须将错误logging到不同的外部依赖项,并通过电子邮件将其发送给Web服务pipe理员。
以下是我们想要在LogAnalyzer中testing的逻辑:
if(fileName.Length<8) { try { service.LogError("Filename too short:" + fileName); } catch (Exception e) { email.SendEmail("a","subject",e.Message); } }
当Web服务引发exception时,如何testingLogAnalyzer正确调用电子邮件服务? 以下是我们所面临的问题:
-
我们如何更换networking服务?
-
我们如何模拟Web服务中的exception,以便testing对电子邮件服务的调用?
-
我们如何知道电子邮件服务是否正确调用?
我们可以通过使用Web服务的存根来处理前两个问题。 为了解决第三个问题,我们可以为电子邮件服务使用模拟对象 。
假货是一个通用的术语,可以用来描述存根或模拟。在我们的testing中,我们将有两个假货。 一个将是电子邮件服务模拟,我们将用它来validation正确的参数发送到电子邮件服务。 另一个将是我们将用来模拟从Web服务抛出的exception的存根。 这是一个存根,因为我们不会使用Web服务假来validationtesting结果,只是为了确保testing运行正确。 电子邮件服务是一个模拟,因为我们会断言它是正确的。
[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,那么解释stub和mock之间可能对你有用的区别的一种方式就是stub属于排列部分,就像排列input状态一样,而mock属于assert部分,因为它们是用来声明结果的。
傻瓜不做任何事情。 它们只是用来填充参数列表,所以你不会得到未定义的或空的错误。 它们也存在以严格types的语言来满足types检查器,以便允许你编译和运行。