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


当前回答

上面有很多有效的答案,但我认为值得一提的是这个表格鲍勃叔叔: https://8thlight.com/blog/uncle-bob/2014/05/14/TheLittleMocker.html

最好的例子解释!

其他回答

A fake is a generic term that can be used to describe either a stub or a mock object (handwritten or otherwise), because they both look like the real object. Whether a fake is a stub or a mock depends on how it’s used in the current test. If it’s used to check an interaction (asserted against), it’s a mock object. Otherwise, it’s a stub. Fakes makes sure test runs smoothly. It means that reader of your future test will understand what will be the behavior of the fake object, without needing to read its source code (without needing to depend on external resource). What does test run smoothly mean? Forexample in below code: public void Analyze(string filename) { if(filename.Length<8) { try { errorService.LogError("long file entered named:" + filename); } catch (Exception e) { mailService.SendEMail("admin@hotmail.com", "ErrorOnWebService", "someerror"); } } } You want to test mailService.SendEMail() method, to do that you need to simulate an Exception in you test method, so you just need to create a Fake Stub errorService class to simulate that result, then your test code will be able to test mailService.SendEMail() method. As you see you need to simulate a result which is from an another External Dependency ErrorService class.

Mock只是测试行为,确保调用了特定的方法。 Stub是特定对象的可测试版本(本质上)。

你说的苹果方式是什么意思?

加上有用的答案,其中最强大的一点使用模拟比潜艇

如果合作者(主代码所依赖的合作者)不在我们的控制之下(例如来自第三方库), 在这种情况下,stub比mock更难编写。

mock既是技术对象,也是功能对象。

这个模拟是技术性的。它确实是由一个模拟库(EasyMock、JMockit和最近的Mockito都以这些库而闻名)创建的,这要归功于字节代码生成。 生成模拟实现的方式是,我们可以在调用方法时将其设置为返回特定值,但也可以执行其他一些事情,例如验证模拟方法是否使用某些特定参数(严格检查)或任何参数(不严格检查)调用。

实例化一个mock:

@Mock Foo fooMock

记录行为:

when(fooMock.hello()).thenReturn("hello you!");

验证调用:

verify(fooMock).hello()

这些显然不是实例化/重写Foo类/行为的自然方式。这就是为什么我提到技术方面。

但是mock也是功能性的,因为它是我们需要从SUT中隔离出来的类的实例。有了记录在上面的行为,我们就可以在SUT中使用它就像使用存根一样。


存根只是一个函数对象:它是我们需要从SUT中隔离出来的类的实例,仅此而已。 这意味着在单元测试期间需要的存根类和所有行为fixture都必须显式地定义。 例如,存根hello()将需要子类化Foo类(或实现它拥有的接口)并重写hello():

public class HelloStub extends Hello{    
  public String hello { 
      return "hello you!"; 
  }
}

如果另一个测试场景需要另一个返回值,我们可能需要定义一个通用的方法来设置返回值:

public class HelloStub extends Hello{    
  public HelloStub(String helloReturn){
       this.helloReturn = helloReturn;
  }
  public String hello { 
      return helloReturn; 
  }
}

其他场景:如果我有一个副作用方法(没有返回),并且我要检查该方法是否被调用,我可能应该在存根类中添加一个布尔值或计数器来计算该方法被调用的次数。


结论

存根通常需要为单元测试编写大量开销/代码。mock因为提供了开箱即用的记录/验证功能而阻止了什么。 这就是为什么现在随着优秀的模拟库的出现,存根方法很少在实践中使用。


关于Martin Fowler文章:当我使用mock和避免存根时,我不认为我是一个“嘲笑主义者”程序员。 但是当真正需要时我使用mock(讨厌的依赖关系),当我测试带有依赖关系的类时,我喜欢测试切片和迷你集成测试,而mock将是一种开销。

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

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