美文网首页
置换测试:Stub,Mock

置换测试:Stub,Mock

作者: Slahser2c | 来源:发表于2018-07-19 21:24 被阅读0次

从测试的角度看,理想情况下,我们的所作的全部测试都是对应了实际的代码,但这并不适用于实际情况,比如每次测试都去访问数据库,或者都去加载许多和待测代码毫无联系的配置文件,这些不但会增加测试的时间开销,同时也会增加测试用例的开发开销。并且这样也难以模拟一些特殊情况下的测试,比如需要特定的网络接入条件,或者当天是某个特殊日期等等。

这时候我们可以用一些模拟的代码来特换实际代码,从而帮助我们进行测试。前两天我司请来的老师来讲的:Stub和Mock,就是两种这样的模拟代码。

但是Stub和Mock的用法由于相似,都是为了替换外部依赖对象,从而经常被理解混淆,或者根本没有分清。但是实际上这是两种完全不同的事物:

  1. 首先它们对于怎么去确定测试结果使用的方式是不同的,其中一个使用行为去确认(behavior verification),一个使用状态去确认(state verification)
  2. 另一方面这是两种将测试和设计结合在一起的方法,一个是自顶向下,一个是自下而上

简言之,Stub更关注交互行为,为了验证待测系统调用目标系统接口的交互行为,而Mock更关注状态,为了验证待测系统调用了目标系统后,目标系统的状态。

public class OrderStateTester {

    private WareHouse warehouse = new WareHouseImpl();
    
    @Before
    protected void setup() throws Exception {
        warehouse.setSize(50);
        warehouse.setLocation("Shang Hai");
    }

    @Test
    public void testOrderIsFilledIfEnoughInWarehouse() {
        Order order = new Order(50);
        order.fill(warehouse);
        assertTrue(order.isFilled());
        assertEquals(0, warehouse.getInventory());
    }

    @Test
    public void testOrderDoseNotRemoveIfNotEnough() {
        Order order = new Order(51);
        order.fill(warehouse);
        assertFalse(order.isFilled());
        assertEquals(50, warehouse.getInventory());
    }
}

类似这样的TestCase是最常见的一种,可以看到我们需要测试的是Order对象。为了这个测试,需要Order跟Warehouse,需要Warehouse的理由有两个:首先需要通过它来配合测试,其次需要它来进行验证(因为order.fill改变了warehouse对象)
如果使用Mock对象会怎么样呢?有很多可用的mock对象库,Mokito,JMock之类的,如果用jMock写一段测试用例则会是:

  public void testFillingRemovesInventoryIfInStock() {
    Order order = new Order(50);
    Mock warehouseMock = new Mock(Warehouse.class);

    warehouseMock.expects(once()).method("hasInventory")
      .with(eq(content),eq(50))
      .will(returnValue(true));
    warehouseMock.expects(once()).method("remove")
      .with(eq(content), eq(50))
      .after("hasInventory");
    order.fill((Warehouse) warehouseMock.proxy());

    warehouseMock.verify();
    assertTrue(order.isFilled());
  }

可以看到在数据准备的阶段,创建了一个Warehouse类的mock对象,接着设置了Mock的期望,这些期望也就是在测试order时会被执行。
在验证阶段,和之前一样可以跑order对象的断言,其次可以调用mock的verify方法,验证它是否像期望的那样去运行。

关键不同点在于怎么样去验证order在于warehouse的交互中做了正确的事。上面的testcase中,我通过warehouse的状态去验证。

如果对于Stub和Mock还是分不清楚的话,或许可以通过老师举得MailSender的case来解释一番:如果我们有一个发送邮件的服务,会和待测对象交互:

public interface MailSender {
  public void send (Message msg);
}  

如果使用Stub去验证:

public class MailSenderStub implements MailSender {
  private List<Message> messages = new ArrayList<Message>();
  public void send (Message msg) {
    messages.add(msg);
  }
  public int numberSent() {
    return messages.size();
  }
}  
@Test
public void testOrder { 
       Order order = new Order(51); 
       MailSenderStub mailer = new MailSenderStub(); 
       order.setMailer(mailer); 
       assertEquals(1 , mailer.numberSent()); 
} 

我们不去关心它是否会发送给正确的人,或者发送的内容是否正确。
如果使用Mock去验证:

@Test
public void testOrderSendsMailIfUnFilled() { 
    Order order = new Order(51); 
    Mock mailer = mock(MailSender.class); 
    Mock warehouse = mock(Warehouse.class); 
    order.setMailer((MailSender)mailer.proxy()); 
    warehouse.expects(once()).method("hasInventory").withAnyArgument() 
    .will(returnValue(false)); 
    order.fill((Warehouse)warehouse.proxy()) 
} 

两种方法都用了别的代码替代真正的MailSender,不同的是Stub采用行为验证,只要发送了邮件即可,而Mock采用了状态验证。


QQ图片20180719212107.jpg

在重新学习了Stub和Mock之后,之前逐渐混淆的概念又有了新的理解,对于目前维护的系统中测试困难的问题,比如测试一段方法需要许多logger对象,或者需要查询DB,发现可以通过完善Stub来解决,而且由于目前系统的开发背景,logger等无关对象(与代码逻辑无关)的实现在多个项目中都几乎相同,所以可以用一套统一的Stub来实现多个系统的测试。而对于那些我们关心它状态的依赖,例如MessageSender,则可以通过Mock的方式实现并验证。

现在的老项目流传下来的祖传test case几乎没有一个能跑通的,下一步的目标就是保证新代码的测试覆盖率,以及在力所能及的范围里面把老代码的测试也搞起来 : )

相关文章

  • 置换测试:Stub,Mock

    从测试的角度看,理想情况下,我们的所作的全部测试都是对应了实际的代码,但这并不适用于实际情况,比如每次测试都去访问...

  • 【CodeTest】Cedar介绍

    学习资料 cedar Kiwi 使用进阶 Mock, Stub, 参数捕获和异步测试 行为驱动开发 置换测试: M...

  • Java单元测试

    概念 Stub和Mock 为什么使用Stub或者Mock? 因为要测试的对象通常会依赖于其他对象,而我们并不需要测...

  • php单元测试进阶(13)- 核心技术 - mock对象 - 同

    php单元测试进阶(13)- 核心技术 - mock对象 - 同时使用mock和stub 本系列文章主要代码与文字...

  • RSpec, Test Double, Mock, and S

    Rspec是ruby的测试框架之一。 Mock和stub都属于Test double,用于测试时,模拟特定的方法或...

  • Mockito入门

    mock使用 mock主要在单元测试的时候用来模拟外部依赖接口的返回,即method stub的作用。 一般而言,...

  • Mockito入门和原理初探

    mock使用 mock主要在单元测试的时候用来模拟外部依赖接口的返回,即method stub的作用。 一般而言,...

  • 测试之stub和mock

    Mock 关注行为验证。细粒度的测试,即代码的逻辑,多数情况下用于单元测试。 Stub 关注状态验证。粗粒度的测试...

  • Mock and Stub

    description: We have a dojo about Mock and Stub.But first...

  • 《Effective Unit Testing》 读书笔记 5

     在上篇读书笔记中介绍了四种测试替身,分别是stub, fake object, test spy, mock o...

网友评论

      本文标题:置换测试:Stub,Mock

      本文链接:https://www.haomeiwen.com/subject/kdjxmftx.html