我知道如何使用这些术语,但我想知道单元测试是否有伪造、模拟和存根的公认定义?如何为您的测试定义这些?描述一下你可能会用到它们的场景。

以下是我如何使用它们:

Fake:实现接口但包含固定数据且没有逻辑的类。只是根据实现返回“好”或“坏”数据。

Mock:实现接口的类,允许从特定方法动态设置返回值/抛出异常,并提供检查特定方法是否被调用/未调用的能力。

Stub:类似于模拟类,只是它不提供验证方法是否被调用的能力。

模拟和存根可以手工生成,也可以由模拟框架生成。假类是手工生成的。我使用模拟主要是为了验证我的类和依赖类之间的交互。一旦我验证了交互并在代码中测试替代路径,我就使用存根。我使用伪类主要是为了抽象出数据依赖关系,或者当mock /存根太乏味而每次都无法设置时。


当前回答

我很惊讶这个问题已经存在了这么长时间,而且还没有人根据Roy Osherove的《单元测试的艺术》给出答案。

在“3.1介绍存根”中将存根定义为:

存根是现有依赖项的可控替代 (或合作者)在系统中。通过使用存根,您可以测试您的代码 直接处理依赖项。

并将存根和mock之间的区别定义为:

关于模拟和存根,要记住的主要事情是,模拟就像存根一样,但是您针对模拟对象进行断言,而不是针对存根进行断言。

Fake只是存根和mock的名称。例如,当您不关心存根和mock之间的区别时。

Osherove's区分存根和mock的方法意味着任何用于测试的伪类都可以是存根或mock。对于特定的测试,它完全取决于您在测试中如何编写检查。

当您的测试检查被测试类中的值,或者实际上除伪类之外的任何地方的值时,伪类被用作存根。它只是为被测试的类提供了可以使用的值,或者直接通过对它的调用返回值,或者间接地通过对它的调用导致副作用(在某些状态下)。 当您的测试检查假的值时,它被用作mock。

FakeX类被用作存根的测试示例:

const pleaseReturn5 = 5;
var fake = new FakeX(pleaseReturn5);
var cut = new ClassUnderTest(fake);

cut.SquareIt;

Assert.AreEqual(25, cut.SomeProperty);

假实例被用作存根,因为Assert根本不使用假实例。

测试类X被用作模拟的例子:

const pleaseReturn5 = 5;
var fake = new FakeX(pleaseReturn5);
var cut = new ClassUnderTest(fake);

cut.SquareIt;

Assert.AreEqual(25, fake.SomeProperty);

在本例中,Assert检查fake的值,使该fake成为mock。

当然,这些例子都是非常做作的,但我看到了这种区别的巨大价值。它让你意识到你是如何测试你的东西的,以及你的测试的依赖关系在哪里。

我同意Osherove的观点

从纯可维护性的角度来看,在我的测试中,使用mock比不使用它们更麻烦。这就是我的经验,但我一直在学习新的东西。

针对fake进行断言是您真正想要避免的事情,因为它会使您的测试高度依赖于一个根本不是被测试类的实现。这意味着类ActualClassUnderTest的测试可能开始中断,因为ClassUsedAsMock的实现改变了。这让我闻到一股恶臭。ActualClassUnderTest的测试最好只在ActualClassUnderTest被更改时中断。

我意识到针对虚假编写断言是一种常见的实践,特别是当您是TDD的嘲笑者类型时。我想我坚定地站在Martin Fowler的古典主义阵营(参见Martin Fowler的“mock不是Stubs”),并且像Osherove一样尽量避免交互测试(这只能通过断言反对虚假来完成)。

关于为什么你应该避免这里定义的mock的有趣阅读,谷歌代表“fowler mockist classical”。你会发现各种各样的观点。

其他回答

存根——为方法调用提供预定义答案的对象。

Mock -一个你设定期望的对象。

假的——一个功能有限的对象(用于测试),例如一个假的web服务。

Test Double是存根、mock和fake的总称。但非正式地,你会经常听到人们简单地称之为mock。

如果您熟悉arrange - act - assert,那么解释存根和mock之间的区别的一种方法可能对您有用,即存根属于arrange部分,因为它们用于排列输入状态,而mock属于assert部分,因为它们用于断言结果。

傻瓜什么都不做。它们只是用来填充参数列表,这样你就不会得到未定义或空错误。它们的存在也是为了满足静态类型语言中的类型检查器,这样您就可以编译和运行。

单元测试——是一种控制单元(类、方法)的测试方法。

Test double -不是一个主对象(来自OOP世界)。这是一种临时创建的实现,用于测试、检查或在开发期间。它们是为关闭被测试单元(方法、类……)的依赖关系而创建的。

测试双工类型:

伪对象是接口(协议)的真实实现或扩展,它使用继承或其他方法来创建- is依赖。通常,它是由开发人员创建的,作为替代某些依赖项的最简单解决方案 存根对象是一个裸对象(0,nil和没有逻辑的方法),具有(由开发人员)预定义的额外状态,以定义返回值。通常是由框架创建的

class StubA: A {
    override func foo() -> String {
        return "My Stub"
    }
}

模拟对象与存根对象非常相似,但是额外的状态在程序执行期间被改变,以检查是否发生了什么(方法被调用,参数,时间,频率……)

class MockA: A {
    var isFooCalled = false
    override func foo() -> String {
        isFooCalled = true
        return "My Mock"
    }
}

间谍对象是一个具有“部分嘲讽性”的真实对象。这意味着除了模拟行为外,您使用的是一个非双精度对象 虚拟对象是运行测试所必需的对象,但该对象的任何变量或方法都没有被调用。

Stub vs mock

马丁·福勒说

存根使用状态验证,而mock使用行为验证,这是不同的。

[Mockito mock vs spy]

正如投票最多的答案所提到的,Martin Fowler在《Mocks不是存根》中讨论了这些区别,特别是副标题《Mocks和存根之间的区别》,所以一定要阅读那篇文章。

与其关注这些东西有何不同,我认为关注为什么这些是不同的概念更有启发性。每一种都有不同的目的。

假货

fake是一种行为“自然”,但不是“真实”的实现。这些都是模糊的概念,所以不同的人对什么是假的有不同的理解。

一个假的例子是内存中的数据库(例如使用sqlite和:memory: store)。您永远不会在生产中使用它(因为数据不是持久化的),但是作为一个数据库在测试环境中使用它是完全足够的。它也比“真正的”数据库轻量级得多。

作为另一个例子,也许您在生产中使用某种对象存储(例如Amazon S3),但是在测试中您可以简单地将对象保存到磁盘上的文件;那么您的“保存到磁盘”实现将是一个假的。(或者您甚至可以通过使用内存文件系统来代替“保存到磁盘”操作。)

作为第三个例子,想象一个提供缓存API的对象;如果一个对象实现了正确的接口,但根本不执行缓存,但总是返回缓存失败,那么这个对象就是一种假的。

pseudo的目的不是为了影响被测系统的行为,而是为了简化测试的实现(通过删除不必要的或重量级的依赖项)。

存根

存根是一种行为“不自然”的实现。它是预先配置的(通常由测试设置),以响应具有特定输出的特定输入。

存根的目的是让测试中的系统进入特定的状态。例如,如果您正在为一些与REST API交互的代码编写测试,那么您可以使用一个始终返回固定响应的API来stub REST API,或者使用一个响应带有特定错误的API请求。通过这种方式,您可以编写关于系统如何对这些状态做出反应的断言测试;例如,如果API返回404错误,测试用户得到的响应。

存根通常被实现为只响应您告诉它响应的确切交互。但是使某些东西成为存根的关键特性是它的目的:存根是关于设置您的测试用例的。

模拟

mock类似于存根,但是添加了验证。mock的目的是断言被测系统如何与依赖项交互。

例如,如果您正在为一个向网站上传文件的系统编写测试,您可以构建一个接受文件的模拟,并且可以使用它来断言上传的文件是正确的。或者,在较小的范围内,通常使用对象的模拟来验证被测系统是否调用了被模拟对象的特定方法。

模拟与交互测试绑定在一起,这是一种特定的测试方法。那些更喜欢测试系统状态而不是系统交互的人会谨慎地使用模拟。

测试双打

假的、存根的和模拟的都属于测试替身的范畴。测试double是您在测试中使用的任何对象或系统,而不是其他东西。大多数自动化软件测试都涉及到使用某种类型的测试替身。其他类型的测试双重对象包括虚值、间谍和I/O黑洞。

根据Vladimir Khorikov的《单元测试原则、实践和模式》一书:

Mocks: help to emulate and examine outcoming interactions. These interactions are calls the SUT makes to its dependencies to change their state. In other words it helps to examine the interaction (behaviour) of SUT and its dependencies. mocks could be : Spy : created manually Mocks : created using framework Stubs: helps to emulate incoming interactions. These interactions are calls the SUT makes to its dependencies to get input data. IN other words it helps to test the data passed to SUT. It could be 3 types Fake: is usually implemented to replace a dependency that doesn’t yet exist. Dummy: is hard-coded value. Stubs: Fledged dependency that you configure to return different values for different scenarios.