美文网首页
置换测试: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

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