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


当前回答

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

其他回答

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

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

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

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

Stub是实现组件接口的对象,但是Stub可以配置为返回适合测试的值,而不是返回调用时组件将返回的值。使用存根,单元测试可以测试一个单元是否可以处理来自合作者的各种返回值。在单元测试中使用存根而不是真正的合作者可以这样表示:

单元测试——>存根

单元测试——>单元——>存根

单元测试对结果和单元状态进行断言

首先,单元测试创建存根并配置其返回值。然后单元测试创建单元并在其上设置存根。现在,单元测试调用单元,而单元又调用存根。最后,单元测试对单元上的方法调用的结果进行断言。

A Mock is like a stub, only it also has methods that make it possible determine what methods where called on the Mock. Using a mock it is thus possible to both test if the unit can handle various return values correctly, and also if the unit uses the collaborator correctly. For instance, you cannot see by the value returned from a dao object whether the data was read from the database using a Statement or a PreparedStatement. Nor can you see if the connection.close() method was called before returning the value. This is possible with mocks. In other words, mocks makes it possible to test a units complete interaction with a collaborator. Not just the collaborator methods that return values used by the unit. Using a mock in a unit test could be expressed like this:

单元测试——>模拟

单元测试—>单元—>模拟

单元测试对单元的结果和状态进行断言

单元测试断言在mock上调用的方法

>>这里

存根用于您在测试中设置的具有预期返回值的方法。 mock用于void方法,这些方法在调用时的Assert中进行验证。

Stubs vs. Mocks Stubs provide specific answers to methods calls ex: myStubbedService.getValues() just return a String needed by the code under test used by code under test to isolate it cannot fail test ex: myStubbedService.getValues() just returns the stubbed value often implement abstract methods Mocks "superset" of stubs; can assert that certain methods are called ex: verify that myMockedService.getValues() is called only once used to test behaviour of code under test can fail test ex: verify that myMockedService.getValues() was called once; verification fails, because myMockedService.getValues() was not called by my tested code often mocks interfaces

前言

有几个对象的定义是不真实的。一般的术语是双重测试。这个术语包括: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 -清理资源。

总结

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

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