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

以下是我如何使用它们:

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

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

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

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


当前回答

这是一个让测试富有表现力的问题。如果我想让测试描述两个对象之间的关系,我就在Mock上设置期望。我存根返回值,如果我设置一个支持对象,让我在测试中有趣的行为。

其他回答

这是一个让测试富有表现力的问题。如果我想让测试描述两个对象之间的关系,我就在Mock上设置期望。我存根返回值,如果我设置一个支持对象,让我在测试中有趣的行为。

Stub, Fakes和Mocks在不同的来源中有不同的含义。我建议你介绍一下你的团队内部术语,并就其含义达成一致。

我认为区分两种方法很重要: -行为验证(暗示行为替换) -最终状态验证(暗示行为模拟)

考虑发送电子邮件以防出错。当做行为验证时,你检查IEmailSender的Send方法是否执行了一次。并且您需要模拟此方法的返回结果,返回发送消息的Id。所以你说:“我期望Send会被调用。我只会为任何调用返回虚拟(或随机)Id”。这就是行为验证: emailSender.Expect(es=> . send (anyThing)).Return((subject,body) => "dummyId")

在进行状态验证时,您需要创建实现IEmailSender的TestEmailSender。并实现发送方法-通过将输入保存到一些数据结构,将用于未来的状态验证,如一些对象的数组,然后它测试你将检查sentemail包含预期的电子邮件。这是状态验证: 断言。emailSender.SentEmails.Count AreEqual (1)

从我的阅读中,我了解到行为验证通常被称为mock。 状态验证通常被称为存根或假。

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

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

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

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

正如投票最多的答案所提到的,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黑洞。

我很惊讶这个问题已经存在了这么长时间,而且还没有人根据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”。你会发现各种各样的观点。