单元测试中Mock与Stub的浅析

Github Repohtml

mocksArentStubsgit

本部分主要介绍所谓的Test Double的概念,而且对其中容易被混用的Mocks与Stubs的概念进行一个阐述。在初期接触到的时候,不少人会把Mock对象与另外一个单元测试中常常用到的Stub对象搞混掉。为了方便更好地理解,这里把全部的所谓的Test Double的概念进行一个说明。咱们先来看一个经常使用的单元测试的用例:github

public class OrderEasyTester extends TestCase {
  private static String TALISKER = "Talisker";
  
  private MockControl warehouseControl;
  private Warehouse warehouseMock;
  
  public void setUp() {
    warehouseControl = MockControl.createControl(Warehouse.class);
    warehouseMock = (Warehouse) warehouseControl.getMock();    
  }

  public void testFillingRemovesInventoryIfInStock() {
    //setup - data
    Order order = new Order(TALISKER, 50);
    
    //setup - expectations
    warehouseMock.hasInventory(TALISKER, 50);
    warehouseControl.setReturnValue(true);
    warehouseMock.remove(TALISKER, 50);
    warehouseControl.replay();

    //exercise
    order.fill(warehouseMock);
    
    //verify
    warehouseControl.verify();
    assertTrue(order.isFilled());
  }

  public void testFillingDoesNotRemoveIfNotEnoughInStock() {
    Order order = new Order(TALISKER, 51);    

    warehouseMock.hasInventory(TALISKER, 51);
    warehouseControl.setReturnValue(false);
    warehouseControl.replay();

    order.fill((Warehouse) warehouseMock);

    assertFalse(order.isFilled());
    warehouseControl.verify();
  }
}

当咱们进行单元测试的时候,咱们会专一于软件中的一个小点,不过问题就是虽然咱们只想进行一个单一模块的测试,可是不得不依赖于其余模块,就好像上面例子中的warehouse。而在我提供的两种不一样的测试用例的编写方案中,第一个是使用了真实的warehouse对象,而第二个使用了所谓的mock的warehouse对象,也意味着并非一个真正的warehouse。使用Mock对象也是一种经常使用的在测试中避免依赖真正的对象的方法,不过像这种在测试中不使用真正对象的方法也有不少。数据库

咱们常常看到的相似的关联的名词会有:stub、mock、fake、dummy。本文中我是打算借鉴Gerard Meszaros的论述,可能并非全部人都怎么描述,不过我以为Gerard Meszaros说的不错。Gerard Meszaros是用Test Double这个术语来称呼这一类用于替换真实对象的模拟对象。Gerard Meszaros具体定义了如下几类double:编程

  • Dummy : 用于传递给调用者可是永远不会被真实使用的对象,一般它们只是用来填满参数列表。单元测试

  • Fake : Fake对象经常与类的实现一块儿起做用,可是只是为了让其余程序可以正常运行,譬如内存数据库就是一个很好的例子。测试

  • Stubs : Stubs一般用于在测试中提供封装好的响应,譬若有时候编程设定的并不会对全部的调用都进行响应。Stubs也会记录下调用的记录,譬如一个email gateway就是一个很好的例子,它能够用来记录全部发送的信息或者它发送的信息的数目。简而言之,Stubs通常是对一个真实对象的封装。code

  • Mocks : Mocks也就是Fowler这篇文章讨论的重点,便是针对设定好的调用方法与须要响应的参数封装出合适的对象。htm

在上述这几种doubles中,只有mocks强调行为验证,其余的通常都是强调状态验证。为了更好地描述这种区别,咱们会对上面的例子进行一些扩展。通常在真实对象不太好交互或者代码尚未写好的时候,咱们会选择使用一个测试的Double。譬如咱们须要测试一个发送邮件的程序是否是可以在发送邮件的时候设定正确的顺序,而咱们确定不但愿真的发邮件出去,这样会被打死的。所以咱们会为咱们的email系统来建立一个test double。这里也是用例子来展现mocks与stubs区别的地方:对象

public interface MailService {
  public void send (Message msg);
}
public class MailServiceStub implements MailService {
  private List<Message> messages = new ArrayList<Message>();
  public void send (Message msg) {
    messages.add(msg);
  }
  public int numberSent() {
    return messages.size();
  }
}

而后就能够进行状态验证了:

class OrderStateTester...

  public void testOrderSendsMailIfUnfilled() {
    Order order = new Order(TALISKER, 51);
    MailServiceStub mailer = new MailServiceStub();
    order.setMailer(mailer);
    order.fill(warehouse);
    assertEquals(1, mailer.numberSent());
  }

固然这是一个很是简单的测试,咱们并无测试它是否发给了正确的人或者发出了正确的内容。而若是使用Mock的话写法就很不同了:

class OrderInteractionTester...

  public void testOrderSendsMailIfUnfilled() {
    Order order = new Order(TALISKER, 51);
    Mock warehouse = mock(Warehouse.class);
    Mock mailer = mock(MailService.class);
    order.setMailer((MailService) mailer.proxy());

    mailer.expects(once()).method("send");
    warehouse.expects(once()).method("hasInventory")
      .withAnyArguments()
      .will(returnValue(false));

    order.fill((Warehouse) warehouse.proxy());
  }
}

在两个例子中咱们都是用了test double来替代真正的mail服务,不一样的在于stub是用的状态验证而mock使用的是行为验证。若是要基于stub编写状态验证的方法,须要写一些额外的代码来进行验证。而Mock对象用的是行为验证,并不须要写太多的额外代码。

相关文章
相关标签/搜索