之间的区别是什么伪造,嘲弄,并且碰?
-
19-08-2019 - |
题
我知道我如何使用这些术语,但我想知道,如果没被接受的定义 伪装, 嘲讽, , 碰 对单元的测试吗?你如何定义这些测试?描述的情况下,你可能会利用每一个。
这里是我如何使用它们:
假:一个类实现一个接口,但含有固定的数据和没有逻辑。简单地返回"良好"或"坏的"数据取决于执行情况。
模拟:一个类实现一个接口,并允许的能力动态设定值回/例外情况扔从特定方法并提供能够检查,如果特定的方法已经被称为/不叫。
存根:像一个模拟舱,但它并不提供能够验证的方法已经被称为/不叫。
嘲笑和桩可方面产生或产生的一种嘲讽的框架。假课程产生的手。我嘲笑使用的主要是验证之间的相互作用我的类和相关类别。我使用存根,一旦我证实的相互作用并测试备用路径通过我的代码。我用假的课程主要是抽象的数据的依赖性或当嘲笑/桩太繁琐的设立的各个时间。
解决方案
您可以得到一些信息:
从马丁福勒约模拟和存根
<强>假强>对象实际上有工作实现,但通常采取一些捷径,这使得它们不适合于生产
存根提供罐装答案在测试过程中进行的调用,通常不响应外界什么程序在测试什么。存根还可以记录有关呼叫,比如记得它“已发送”邮件的电子邮件网关存根信息,也可能是唯一的多少消息“发送”。
嘲弄是我们在这里谈论什么:对象预编程与形成他们预计接收呼叫的规格期望
从 xunitpattern :
<强>假强>:我们获取或建立一个非常轻量级实现的相同的功能由该SUT取决于并指示SUT用它代替所述真实的成分来提供
存根:此实现配置为从与将行使未经测试的代码(见页X生产的漏洞)的SUT中的值(或异常)的SUT呼吁作出响应。用于使用测试存根是具有引起不能控制SUT的间接输入未经测试的代码A密钥指示
<强> Mock对象强>实现相同的接口在其上SUT(待测系统)取决于一个对象。我们可以使用模拟对象作为观测点,当我们需要做的动作验证,以避免未经检验的要求(见页X生产错误)所造成的无法观察被测系统调用方法的副作用。
个人
我尝试通过使用简化:模拟和存根。我用模拟当它返回它被设置为被测试类值的对象。我用存根来模拟一个接口或抽象类进行测试。事实上,它并没有真正不管你叫它什么,他们是不是在生产中使用的所有类,并作为实用类进行测试。
其他提示
<强>存根强> - 提供预定义的解答方法调用的对象。
<强>模拟强> - 在其上设置的期望的对象
<强>假强> - 具有有限能力的对象(用于测试的目的),例如假的Web服务。
测试双重为存根,嘲笑和赝品的总称。但是非正式的,你会经常听到人们只需拨打他们嘲笑。
我很惊讶,这个问题已经很久没有人尚未提供答复的基础上 罗伊Osherove的"艺术单元的测试".
在"3.1介绍桩"定义的一桩为:
存根的是一个可控制的更换现有的依赖 (或合作者)的系统。通过使用短线,可以测试你的代码没有 处理与依赖。
和定义的差异之间存根和嘲笑为:
最主要的是要记住嘲笑对桩是嘲笑就像桩,但是你一主张对抗模拟对象,而不主张对抗一个存根。
假的,只是所使用的名称为存根和嘲笑.例如当你不关心之间的区别存根和嘲笑.
的方式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
改变。
我认识到,编写声称对假是一种常见的做法,特别是当你是一个mockist类型使用户。我想我坚定地马丁*福勒中古典主义者营地(见 马丁*福勒的"嘲笑不是桩")和喜欢Osherove避免相互作用的测试(这只能通过声明,对假)尽可能多的。
对于阅读的乐趣,为什么你应该避免嘲笑的定义在这里,谷歌"fowler mockist古典主义".你会找到大量的意见。
如由顶部投答案提到的,马丁福勒讨论模拟的功能不存根和特别是子目嘲笑和存根之间的差所以一定要读到了那篇文章。
而不是集中于的如何的这些东西都是不同的,我认为这是更具启发关注的为什么的这些是不同的概念。每个存在对于不同的目的。
伪造品
一个的假是用于表现“天然”的实现,而不是“真实的”。这些都是模糊概念,因此不同的人有什么使事情有不同的理解是假的。
一个假的一个例子是一个内存数据库(例如,使用与:memory:
商店源码)。你绝不会用这个生产(由于数据不保留),但它是完全足够的数据库,在测试环境中使用。它也更轻便多了一个“真正”的数据库。
作为另一实例,或许使用某种对象存储(例如亚马逊S3)中生产的,但在测试可以简单地保存对象,以磁盘上的文件;那么你的“保存到磁盘”的实施将是一个假的。 (或者你可以甚至假的通过使用一个内存中的文件系统,而不是“保存到磁盘”操作。)
作为第三个例子,假设一个目的,提供了一个高速缓存API;实现了正确的接口,但是,简单地在所有不进行缓存,但总是一个对象返回高速缓存未命中将是一种假的。
<强>假的目的是不的影响下测试强>的系统的行为,而是简化测试的实施(通过去除不必要的或重量级依赖关系)。
存根
一个的存根是用于表现 “不自然” 的实现。它被预先配置(通常由测试设置),以与特定的输出的特定输入回应。
存根的目的是让你的系统测试进入特定的状态。例如,如果你正在编写一个测试一些代码,用REST API,你可以交互的存根出的REST API与总是返回一个罐头回应,或是响应与特定的错误的API请求的API。这样,你可以写测试,作出关于系统如何应对这些国家的断言;例如,测试响应用户得到如果API返回404错误。
一个存根通常被实现到只有你告诉它响应的精确交互响应。但关键的功能,使东西存根是它的目的的:存根是所有关于设置你的测试用例
嘲笑
一个的模拟强>类似于存根,但与验证加入英寸的一个模拟的目的是做出关于您的被测系统如何相互作用的断言与依赖强>
例如,如果你正在编写一个测试为上传文件到网站的系统,你可以建立一个的模拟的接受一个文件,你可以用它来断言,上传的文件是正确。或者,在较小规模上,这是通常使用的对象的模拟,以验证测试中的系统调用嘲笑对象的特定方法。
嘲笑是绑相互作用测试,这是一个具体的测试方法。谁喜欢的人进行测试的系统状态的而不是系统交互的将使用模拟考试谨慎,如果在所有。
测试加倍
伪造品,短截线,和嘲笑都属于测试双打的类别。测试双是在测试中的使用任何物体或系统,而不是的东西。大多数汽车个配合的软件测试包括使用一些这样或那样的测试双打的。一些其他种类的测试双打的包括的虚拟值下,的探子,然后I / O的黑洞强>
为了说明所使用的存根和嘲笑,我想也包括例如根据罗伊Osherove的"艺术单元的测试".
想象一下,我们有一个LogAnalyzer应用程序,它拥有的唯一功能的打印的记录。它不仅需要跟一个网服务,但如果该网服务,引发错误,LogAnalyzer已登录在错误的一个不同的外部依赖性,发送电子邮件网服务管理员。
这里的逻辑,我们想到测试的内部LogAnalyzer:
if(fileName.Length<8)
{
try
{
service.LogError("Filename too short:" + fileName);
}
catch (Exception e)
{
email.SendEmail("a","subject",e.Message);
}
}
如何做你的测试,LogAnalyzer呼叫的电子邮件服务时能正确的网服务,引发一个例外吗?这里都是问题,我们面临的有:
我们怎么可以替代网络服务?
我们如何能够模拟异常从网服务,以便我们可以 测试通话的电子邮件服务?
我们怎么会知道电子邮件服务被称为正确,或在 所有的?
我们可以处理与第二个问题通过 使用短线网服务.要解决的第三个问题,我们可以 使用一个模拟对象的电子邮件服务.
一个假的是一个通用术语,可以用来描述一桩或嘲笑。在我们测试,我们就会有两个赝品。一个将以电子邮件服务模拟,这是我们将用于验证正确的参数分别发送的电子邮件服务。其他将一短截说我们会使用的模拟异常引发从网服务。这是一个短,因为我们不会被使用的网络服务假的验证试验结果,只有确保测试能够正常运行。电子邮件服务是一种嘲笑,因为我们将断言反对它,它被称为正确。
[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);
}
}
这使得测试表现的问题。我设置一个模拟的期望,如果我想测试来描述两个对象之间的关系。我存根返回值,如果我设置了一个支撑物,让我在测试的有趣的行为。
这是你就可以断言的东西,叫做模拟对象和其他一切只是帮助了试运行,是一个存根
如果您熟悉安排-ACT-断言,则说明可能对您有用存根和模拟之间的差别的一种方法,是存根属于安排部分,因为它们是安排输入状态,因为它们是用于断言结果与嘲笑属于断言部分。
傻瓜没有做任何事情。他们只是为了填补参数列表,这样你就不会得到未定义或为空的错误。他们也存在,以满足严格的类型语言的类型检查,这样就可以允许编译和运行。
<强>存根和<强>假强>是,他们可以基于输入参数改变它们的响应对象。它们之间的主要区别是假的更接近真实世界的实现比存根。存根包含基本上与预期的请求硬编码的响应。让看到的示例:
public class MyUnitTest {
@Test
public void testConcatenate() {
StubDependency stubDependency = new StubDependency();
int result = stubDependency.toNumber("one", "two");
assertEquals("onetwo", result);
}
}
public class StubDependency() {
public int toNumber(string param) {
if (param == “one”) {
return 1;
}
if (param == “two”) {
return 2;
}
}
}
一个的模拟强>是假货和存根起来的步骤。嘲弄提供相同的功能存根,但更复杂。他们可以在一定要叫上他们的API什么顺序方法决定为它们定义的规则。大多数嘲弄可以跟踪多少次的方法被调用,并能反应基于这些信息。嘲笑大致知道每个呼叫的情况下,可以在不同的情况做出不同的反应。正因为如此,嘲笑要求他们嘲讽类的一些知识。存根通常不能跟踪方法调用多少次或以什么顺序的方法的顺序被调用。有模拟如下:
public class MockADependency {
private int ShouldCallTwice;
private boolean ShouldCallAtEnd;
private boolean ShouldCallFirst;
public int StringToInteger(String s) {
if (s == "abc") {
return 1;
}
if (s == "xyz") {
return 2;
}
return 0;
}
public void ShouldCallFirst() {
if ((ShouldCallTwice > 0) || ShouldCallAtEnd)
throw new AssertionException("ShouldCallFirst not first thod called");
ShouldCallFirst = true;
}
public int ShouldCallTwice(string s) {
if (!ShouldCallFirst)
throw new AssertionException("ShouldCallTwice called before ShouldCallFirst");
if (ShouldCallAtEnd)
throw new AssertionException("ShouldCallTwice called after ShouldCallAtEnd");
if (ShouldCallTwice >= 2)
throw new AssertionException("ShouldCallTwice called more than twice");
ShouldCallTwice++;
return StringToInteger(s);
}
public void ShouldCallAtEnd() {
if (!ShouldCallFirst)
throw new AssertionException("ShouldCallAtEnd called before ShouldCallFirst");
if (ShouldCallTwice != 2) throw new AssertionException("ShouldCallTwice not called twice");
ShouldCallAtEnd = true;
}
}