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


当前回答

有很多很棒的答案,我喜欢这个,所以我把它做成了一个表格。

Dummy Stub Mock Fake
API O O O O
States X O O O
Values X X O O
Behavior X X X O

其他回答

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

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

stub ->用于测试数据

mock ->用于测试行为


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

Stub

存根是用来伪造具有预先编程行为的方法的对象。为了避免不必要的副作用(例如,存根可能会进行一个虚假的获取调用,返回预先编程的响应,而不实际向服务器发出请求),您可能会使用这个方法来代替现有的方法。

Mock

mock是用来模拟具有预编程行为和预编程期望的方法的对象。如果这些期望没有得到满足,那么mock将导致测试失败(例如,mock可以进行一个虚假的获取调用,返回预先编程的响应,而不实际向服务器发出请求,例如,第一个参数是“http://localhost:3008/”,否则测试将失败。)

区别

与模拟不同,存根没有预先编程的可能导致测试失败的期望。

前言

有几个对象的定义是不真实的。一般的术语是双重测试。这个术语包括:dummy, fake, stub, mock。

参考

根据Martin Fowler的文章:

Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists. Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example). Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test. Stubs may also record information about calls, such as an email gateway stub that remembers the messages it 'sent', or maybe only how many messages it 'sent'. Mocks are what we are talking about here: objects pre-programmed with expectations which form a specification of the calls they are expected to receive.

风格

模拟vs存根=行为测试vs状态测试

原则

根据每个测试只测试一件事的原则,一个测试中可能有几个存根,但一般只有一个mock。

生命周期

使用存根测试生命周期:

准备正在测试的对象和它的存根合作者。 锻炼——测试功能。 验证状态——使用断言来检查对象的状态。 Teardown -清理资源。

使用mock测试生命周期:

设置数据-准备正在测试的对象。 设置期望-在mock中准备主对象使用的期望。 锻炼——测试功能。 验证期望——验证在mock中调用了正确的方法。 验证状态——使用断言来检查对象的状态。 Teardown -清理资源。

总结

模拟测试和存根测试都给出了这个问题的答案:结果是什么?

使用模拟进行测试也感兴趣:结果是如何实现的?

使用一个心智模型确实帮助我理解了这一点,而不是所有的解释和文章,这些都不能“理解”。

想象一下,你的孩子桌子上有一个玻璃盘子,他开始玩它。现在,你害怕它会破裂。所以,你给了他一个塑料盘子。这将是一个Mock(相同的行为,相同的接口,“更软的”实现)。

现在,假设你没有塑料替代品,所以你解释说“如果你继续玩它,它会碎的!”这是一个Stub,您预先提供了一个预定义的状态。

哑巴就是他根本不用的叉子…间谍可能是提供你已经使用过的有效解释。