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


当前回答

假设你有一个名为EmployeeService的类,你想测试它,并且它对一个名为EmployeeDao的接口有一个依赖:

public class EmployeeService{
   private EmployeeDao dao;
   public EmployeeService(Dao dao){this.dao = dao;}

   public String getEmployeeName(int id){
     Employee emp = bar.goToDatabaseAndBringTheEmployeeWithId(id);
     return emp != null?emp.getFullName:null;
   }
   //Further state and behavior
}

public interface EmployeeDao{
  Employee goToDatabaseAndBringTheEmployeeWithId(int id);
}

在测试类内部:

public class EmployeeServiceTest{
   EmployeeService service;
   EmployeeDao mockDao = Mockito.mock(EmployeeDao.class);//Line 3

   @Before
   public void setUp(){
     service = new EmployeeService(mockDao);
   }
   //Tests
   //....
}

在上面的测试类的第3行中,我们对mock框架(在本例中是Mockito)说“嘿,Mockito,给我做一个具有EmployeeDao功能的对象。”框架将创建一个对象,它有goToDatabaseAndBringTheEmployeeWithId方法,但实际上没有主体。你的工作就是指导那个mock做什么。这是一个嘲弄。

但是你也可以创建一个实现EmployeeDao接口的类,并在测试类中使用它:

public EmployeeDaoStub implements EmployeeDao{
   public Employee goToDatabaseAndBringTheEmployeeWithId(int id){
      //No trip to DB, just returning a dummy Employee object
      return new Employee("John","Woo","123 Lincoln str");
   }
}

在你的测试类中,这次使用stub而不是mock:

public class EmployeeServiceTest{
   EmployeeService service;
   EmployeeDao daoStub = new EmployeeDaoStub();//Line 3

   @Before
   public void setUp(){
     service = new EmployeeService(daoStub);
   }
   //Tests
   //....
}

因此,为了包装这一切,存根是您(或其他人)专门创建的类,以模仿某些依赖关系,只是为了拥有所需的状态。是的,正如其他人所说,它主要是关于一个状态,而mock通常是由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

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

让我试着用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

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

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

我在阅读《单元测试的艺术》时,偶然发现了以下定义:

fake是一个通用术语,可以用来描述存根或模拟对象(手写或其他),因为它们看起来都像真实的对象。赝品是存根还是mock取决于它在当前测试中的使用方式。如果它用于检查交互(根据其断言),则它是一个模拟对象。否则,它就是存根。

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

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

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

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