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


当前回答

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

其他回答

我认为他们之间最重要的区别是他们的意图。

让我试着用WHY stub和WHY mock来解释它

假设我正在为我的mac twitter客户端的公共时间轴控制器编写测试代码

下面是测试示例代码

twitter_api.stub(:public_timeline).and_return(public_timeline_array)
client_ui.should_receive(:insert_timeline_above).with(public_timeline_array)
controller.refresh_public_timeline

STUB:到twitter API的网络连接非常慢,这使得我的测试很慢。我知道它将返回时间轴,所以我制作了一个模拟HTTP twitter API的存根,这样我的测试将非常快地运行它,即使我离线也可以运行测试。 MOCK:我还没有写任何我的UI方法,我不确定我需要为我的UI对象写什么方法。我希望通过编写测试代码了解我的控制器如何与我的ui对象协作。

通过编写mock,您可以通过验证期望是否满足来发现对象的协作关系,而stub仅模拟对象的行为。

如果您想了解更多关于模拟的知识,我建议您阅读这篇文章:http://jmock.org/oopsla2004.pdf

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

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

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

测试双打:

Fake: Fakes are objects that have working implementations, but not the same as production one. Such as: in-memory implementation of Data Access Object or Repository. Stub: Stub is an object that holds predefined data and uses it to answer calls during tests. Such as: an object that needs to grab some data from the database to respond to a method call. Mocks: Mocks are objects that register calls they receive. In test assertion, we can verify on Mocks that all expected actions were performed. Such as: a functionality that calls e-mail sending service. for more just check this.

Stub帮助我们运行测试。怎么做?它给出了有助于运行测试的值。这些值本身不是真实的,我们创建这些值只是为了运行测试。例如,我们创建一个HashMap来提供与数据库表中的值相似的值。因此,我们不直接与数据库交互,而是与Hashmap交互。

Mock是一个运行测试的伪对象。我们在这里输入assert。