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

以下是我如何使用它们:

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

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

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

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


当前回答

它们都被称为Test Doubles,用于注入测试用例所需的依赖项。

存根: 它已经有一个预定义的行为来设置您的期望 例如,stub只返回API响应的成功案例

mock是更聪明的存根。您验证您的测试通过了它。 所以你可以让amock返回成功或失败成功取决于你的测试用例可以改变的条件。

其他回答

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

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]

你可以得到一些信息:

来自马丁·福勒关于莫克和斯塔克的信

假对象实际上有工作实现,但通常采取一些捷径,这使得它们不适合生产

存根为测试过程中拨打的电话提供了预先准备好的答案,通常不会对测试程序之外的任何内容做出任何回应。存根还可以记录有关通话的信息,例如电子邮件网关存根可以记住它“发送”的消息,或者可能只记住它“发送”了多少消息。

mock就是我们在这里讨论的对象:用期望预编程的对象,这些期望形成了期望它们接收的调用的规范。

从xunitpattern:

Fake:我们获得或构建SUT所依赖的组件所提供的相同功能的非常轻量级的实现,并指示SUT使用它而不是真正的功能。

存根:这个实现被配置为响应来自SUT的调用,使用值(或异常)在SUT中执行未测试的代码(参见第X页的生产bug)。使用测试存根的一个关键指示是由于无法控制SUT的间接输入而导致的未测试代码

模拟对象,它实现了与SUT(被测系统)所依赖的对象相同的接口。当我们需要进行行为验证时,我们可以使用模拟对象作为观察点,以避免由于无法观察在SUT上调用方法的副作用而导致的未测试需求(参见第X页的生产bug)。

就我个人而言

我试图通过使用:Mock和Stub来简化。当它是一个返回被测试类的值的对象时,我使用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”。你会发现各种各样的观点。