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

以下是我如何使用它们:

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

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

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

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


当前回答

你可以得到一些信息:

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

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

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

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

从xunitpattern:

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

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

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

就我个人而言

我试图通过使用:Mock和Stub来简化。当它是一个返回被测试类的值的对象时,我使用Mock。我使用Stub来模拟要测试的接口或抽象类。事实上,如何称呼它并不重要,它们都是在生产中不使用的类,而被用作测试的实用程序类。

其他回答

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

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

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

Test Double是存根、mock和fake的总称。但非正式地,你会经常听到人们简单地称之为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。 状态验证通常被称为存根或假。

Stub和fake是对象,因为它们可以根据输入参数改变响应。它们之间的主要区别是Fake比存根更接近真实世界的实现。存根基本上包含对预期请求的硬编码响应。让我们看一个例子:

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;
  }
 }
}

A mock is a step up from fakes and stubs. Mocks provide the same functionality as stubs but are more complex. They can have rules defined for them that dictate in what order methods on their API must be called. Most mocks can track how many times a method was called and can react based on that information. Mocks generally know the context of each call and can react differently in different situations. Because of this, mocks require some knowledge of the class they are mocking. a stub generally cannot track how many times a method was called or in what order a sequence of methods was called. A mock looks like:

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;
 }

}

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