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


当前回答

如果你把它比作调试:

Stub类似于确保一个方法返回正确的值 Mock实际上就像进入方法,在返回正确值之前确保里面的所有内容都是正确的。

其他回答

存根是一个简单的伪对象。它只是确保测试顺利进行。 mock是更聪明的存根。您验证您的测试通过了它。

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

最好的例子解释!

看了上面所有的解释,让我试着总结一下:

Stub:让测试运行的一段虚拟代码,但您并不关心它会发生什么。替代实际工作代码。 Mock:在测试中验证是否正确调用的一段虚拟代码。替代实际工作代码。 间谍:一段虚拟代码,用于拦截和验证对实际工作代码的某些调用,从而避免替换所有实际代码。

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

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将是一种开销。