单元测试:模拟框架与存根实现

Unit test: Mocking framework vs Stub implementation

给定需要使用特定服务的单元测试class,似乎有不同的方法来伪造服务的行为,例如使用模拟框架,或实现存根class.

例如,read/write 到磁盘的服务:

public interface FileServiceInterface {
  public void write(String text);
  public String read(String fileName);
}

我可能有一个 class 来伪造它的行为,然后在测试中使用它 class,注入存根而不是真正的实现:

public class FileServiceStub implements FileServiceInterface {
  ConcurrentHashMap<String, String> content = new ConcurrentHashMap<>();

  public void write(String fileName, String text) { 
    content.put(fileName, text); 
  }

  public String read(String fileName) { 
    return content.get(fileName); 
  }
}

其他选项是让 Mockito(例如)在测试中直接拦截对服务的调用 class:

public class TestExample {

  @Mock
  private FileServiceImpl service; // A real implementation of the service

  @Test
  void doSomeReadTesting() { 
    when(service.read(any(String.class))).thenReturn("something");
    ...
  }
}

我想知道这些备选方案中哪一个是最好的(或目前最被接受的)方法,以及是否有任何 other/better 选项。谢谢

简短回答: 取决于用例,当您需要检查行为时使用 Mock 否则在 [=67 的情况下=] 测试使用 Stub
状态验证中,您让被测对象在提供所有必要的存根后执行特定操作。当它结束时,您检查对象的状态并验证它是否是预期的状态。
行为验证中,您可以准确指定要调用的方法,因此验证的不是结束状态是否正确,而是执行的步骤顺序是否正确。

Martin Fowler 在
文章 Mocks Aren't Stubs 中对 StubsMocks 进行了很好的比较。
为了测试目的,有几种类型的假装对象代替真实对象:

  • 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.
  • Spies are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent.
  • Mocks objects pre-programmed with expectations which form a specification of the calls they are expected to receive.

在您的情况下,我们有 FakeMock 对象类型。 它们看起来都像真实的对象,但与 Mocks 不同,其他类型没有 pre-programmed 可能无法通过测试的期望。 StubFake 只关心最终状态 - 而不是该状态是如何导出的。
所以我们有两种不同的测试设计风格。

基于状态的 测试更多black-boxed。他们实际上并不关心被测系统 (SUT) 如何获得结果,只要它是正确的即可。这使它们更能抵抗变化,减少与设计的耦合。

但有时您会 运行 做一些非常难以使用 状态验证 的事情,即使它们不是尴尬的协作。缓存就是一个很好的例子。缓存的全部意义在于,您无法从其状态判断缓存命中还是未命中 - 在这种情况下,行为验证 将是明智的选择。

Mock 的另一个好处是能够定义对期望的宽松约束 - 您可以使用 any()anyString()withAnyArguments().很灵活。

What's the difference between a mock & stub?