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

以下是我如何使用它们:

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

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

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

模拟和存根可以手工生成,也可以由模拟框架生成。假类是手工生成的。我使用模拟主要是为了验证我的类和依赖类之间的交互。一旦我验证了交互并在代码中测试替代路径,我就使用存根。我使用伪类主要是为了抽象出数据依赖关系,或者当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。

只有在使用像Moq这样的Mock框架时才使用Mock,因为当它使用new Mock<ISomething>()创建时,将其称为Fake似乎是不正确的-虽然从技术上讲,您可以使用Mock框架创建stub或Fakes,但在这种情况下这样称呼它似乎有点愚蠢-它必须是Mock。

其他都是假的。如果一个Fake可以被总结为一个功能降低的实现,那么我认为一个Stub也可以是一个Fake(如果不是,谁在乎呢,每个人都知道我的意思,而且从来没有人说过“我认为你会发现那是一个Stub”)

你可以得到一些信息:

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

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

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

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

从xunitpattern:

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

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

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

就我个人而言

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

我很惊讶这个问题已经存在了这么长时间,而且还没有人根据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上设置期望。我存根返回值,如果我设置一个支持对象,让我在测试中有趣的行为。

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

}