我读过各种关于测试中模仿和存根的文章,包括Martin Fowler的《Mocks Aren't Stubs》,但我仍然不理解其中的区别。


当前回答

Mock——Mock拦截对一个方法或函数的调用(或者像模拟类那样的一组方法和函数)。它不是该方法或函数的替代品。在那次拦截中,mock可以做任何它想做的事情,比如记录输入和输出,决定短路调用,更改返回值,等等。

存根——存根是一个有效的方法或函数(或一组方法和函数,就像存根类一样)的完整工作实现,它与它存根的方法、函数或一组方法和函数具有相同的接口/签名。stub实现通常只会做在单元测试上下文中可以接受的事情,这意味着它不会做IO,同时模仿它要stub的东西的行为。

其他回答

Stub

存根是保存预定义数据并在测试期间使用它应答调用的对象。当您不能或不想涉及与真实数据对应或具有不良副作用的对象时,可以使用它。

一个例子可以是需要从数据库获取一些数据以响应方法调用的对象。我们引入了存根而不是实际对象,并定义了应该返回什么数据。


Stub的例子:

public class GradesService {

   private final Gradebook gradebook;

   public GradesService(Gradebook gradebook) {
       this.gradebook = gradebook;
   }

   Double averageGrades(Student student) {
       return average(gradebook.gradesFor(student));
   }
}

不是从Gradebook store调用数据库来获取真实的学生成绩,而是预先配置将返回的成绩存根。定义足够的数据来测试平均计算算法。

public class GradesServiceTest {

   private Student student;
   private Gradebook gradebook;

   @Before
   public void setUp() throws Exception {
       gradebook = mock(Gradebook.class);
       student = new Student();
   }

   @Test
   public void calculates_grades_average_for_student() {
       //stubbing gradebook
       when(gradebook.gradesFor(student)).thenReturn(grades(8, 6, 10)); 

       double averageGrades = new GradesService(gradebook).averageGrades(student);

       assertThat(averageGrades).isEqualTo(8.0);
   }
}


Mock

mock是注册它们接收到的调用的对象。在测试断言中,您可以在mock上验证是否执行了所有预期的操作。当您不想调用产品代码或没有简单的方法来验证预期的代码是否被执行时,您可以使用模拟。没有返回值,也没有检查系统状态更改的简单方法。调用电子邮件发送服务的功能就是一个例子。

您不希望每次运行测试时都发送电子邮件。此外,在测试中验证发送的电子邮件是否正确并不容易。您唯一能做的就是验证在我们的测试中执行的功能的输出。在其他情况下,验证是否调用了电子邮件发送服务。


Mock示例:

public class SecurityCentral {

   private final Window window;
   private final Door door;

   public SecurityCentral(Window window, Door door) {
       this.window = window;
       this.door = door;
   }

   void securityOn() {
       window.close();
       door.close();
   }
}

你不想关上真正的门来测试安全方法是否有效,对吧?相反,在测试代码中放置门和窗模拟对象。

public class SecurityCentralTest {

   Window windowMock = mock(Window.class);
   Door doorMock = mock(Door.class);

   @Test
   public void enabling_security_locks_windows_and_doors() {
       SecurityCentral securityCentral = new SecurityCentral(windowMock, doorMock);

       securityCentral.securityOn();

       verify(doorMock).close();
       verify(windowMock).close();
   }
}

非常感谢michaowallipski的好文章。欲进一步阅读:

测试双-马丁福勒https://martinfowler.com/bliki/TestDouble.html 测试Double - xUnit模式http://xunitpatterns.com/Test%20Double.html 嘲笑不是存根-马丁福勒https://martinfowler.com/articles/mocksArentStubs.html 命令查询分离- Martin Fowler https://martinfowler.com/bliki/CommandQuerySeparation.html

我认为他们之间最重要的区别是他们的意图。

让我试着用WHY stub和WHY mock来解释它

假设我正在为我的mac twitter客户端的公共时间轴控制器编写测试代码

下面是测试示例代码

twitter_api.stub(:public_timeline).and_return(public_timeline_array)
client_ui.should_receive(:insert_timeline_above).with(public_timeline_array)
controller.refresh_public_timeline

STUB:到twitter API的网络连接非常慢,这使得我的测试很慢。我知道它将返回时间轴,所以我制作了一个模拟HTTP twitter API的存根,这样我的测试将非常快地运行它,即使我离线也可以运行测试。 MOCK:我还没有写任何我的UI方法,我不确定我需要为我的UI对象写什么方法。我希望通过编写测试代码了解我的控制器如何与我的ui对象协作。

通过编写mock,您可以通过验证期望是否满足来发现对象的协作关系,而stub仅模拟对象的行为。

如果您想了解更多关于模拟的知识,我建议您阅读这篇文章:http://jmock.org/oopsla2004.pdf

在codeschool.com的课程《Rails僵尸测试》中,他们给出了这些术语的定义:

Stub

用于将方法替换为返回指定结果的代码。

Mock

带有调用方法的断言的存根。

因此,正如Sean Copenhaver在他的回答中所描述的那样,不同之处在于mock设置了期望(即做出断言,关于是否或如何调用它们)。

下面是对每一个的描述,然后是真实世界的样本。

Dummy - just bogus values to satisfy the API. Example: If you're testing a method of a class which requires many mandatory parameters in a constructor which have no effect on your test, then you may create dummy objects for the purpose of creating new instances of a class. Fake - create a test implementation of a class which may have a dependency on some external infrastructure. (It's good practice that your unit test does NOT actually interact with external infrastructure.) Example: Create fake implementation for accessing a database, replace it with in-memory collection. Stub - override methods to return hard-coded values, also referred to as state-based. Example: Your test class depends on a method Calculate() taking 5 minutes to complete. Rather than wait for 5 minutes you can replace its real implementation with stub that returns hard-coded values; taking only a small fraction of the time. Mock - very similar to Stub but interaction-based rather than state-based. This means you don't expect from Mock to return some value, but to assume that specific order of method calls are made. Example: You're testing a user registration class. After calling Save, it should call SendConfirmationEmail.

存根和Mock实际上是Mock的子类型,两者都交换了实际实现和测试实现,但出于不同的、特定的原因。

存根和模拟都覆盖外部依赖项,但区别在于

stub ->用于测试数据

mock ->用于测试行为


不测试任何东西(只是用空方法覆盖功能,例如替换Logger以避免在测试时记录任何噪音)