• 模拟测试框架之Mockito使用及原理分析


    前言

    当我们进行单元测试时,可能某个依赖的服务还没有开发完成(如RPC或HTTP调用),这种情况下我们就可以对依赖服务创建一个模拟对象,这样我们就可以更加关注于当前的测试类,而不是依赖的服务类。Mockito是一个强大的模拟测试框架,可以让我们很方便的创建模拟对象并进行行为验证。

    添加maven依赖

    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-core</artifactId>
      <version>3.3.3</version>
    </dependency>
    

    创建模拟对象

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import org.mockito.Mockito;
    
    public class TestMock2 {
    
      public static void main(String[] args) {
        List<String> mockList = Mockito.mock(List.class);
        System.out.println(mockList.getClass());
        System.out.println(Arrays.toString(mockList.getClass().getInterfaces()));
        mockList = Mockito.mock(ArrayList.class);
        System.out.println(mockList.getClass());
        System.out.println(Arrays.toString(mockList.getClass().getInterfaces()));
      }
    
    }
    

    既可以对接口创建模拟对象,也可以对具体的实现类创建模拟对象,Mockito底层使用ByteBuddy库来创建代理类,使用Objenesis库来实例化对象。
    ByteBuddy是一个代码生成和操作的类库,类似于Cglib、javassist,底层也是ASM库,官网
    Objenesis是一个小的java库,可以让我们绕过构造器来实例化对象,Spring通过Cglib创建代理对象的过程中就使用到了Objenesis,
    更多信息可以查看java中Objenesis库简单使用

    创建部分模拟对象

    import java.util.ArrayList;
    import java.util.List;
    import org.mockito.Mockito;
    
    public class TestMock4 {
    
      public static void main(String[] args) {
        List<String> mockList = Mockito.mock(ArrayList.class);
        List<String> spyList = Mockito.spy(ArrayList.class);
        mockList.add("hello");
        spyList.add("hello");
        System.out.println(mockList.get(0));//null
        System.out.println(spyList.get(0));//hello
      }
    
    }
    

    spy()方法和mock()的区别在于

    • mock()方法在没有找到对应的配置行为时,返回默认结果,如int类型返回0
    • spy()方法在没有找到对应的配置行为时,委托给被代理对象处理,这里就是ArrayList。如果被代理对象为接口,也是返回默认结果。
      具体可以查看CallsRealMethods类实现。

    配置方法行为

    import java.util.List;
    import org.mockito.Mockito;
    
    public class TestMock2 {
    
      public static void main(String[] args) {
        List<String> mockList = Mockito.mock(List.class);
        Mockito.when(mockList.size()).thenReturn(1);
        System.out.println(mockList.size());
        Mockito.when(mockList.get(0)).thenReturn("hello");
        System.out.println(mockList.get(0));
        //抛出异常
        Mockito.when(mockList.get(0)).thenThrow(new RuntimeException("error"));
        System.out.println(mockList.get(0));
      }
    
    }
    

    为List.size()行为配置一个结果1,下次调用此方法时直接返回此结果。
    没有配置行为时,使用默认结果,配置在DefaultMockitoConfiguration的ReturnsEmptyValues类中,如对int值返回0,对Iterable类型,返回一个空的ArrayList。
    接下来分析一下原理:

    1. 当我们调用List.size()时,Mockito会拦截此方法,将方法调用(List.size())的详细信息保存到模拟对象的上下文中
    2. 调用Mockito.when()方法时,会从上下文(具体为MockingProgressImpl)中获取到最后一次方法调用(List.size())信息
    3. 将thenReturn()的参数作为结果保存起来,下次调用时直接获取。

    方法调用容器为InvocationContainerImpl,其中包含一系列StubbedInvocationMatcher对象,每一个对象都是一个方法调用和具体结果的封装。

    验证方法调用次数

    import java.util.List;
    import org.mockito.Mockito;
    
    public class TestMock3 {
    
      public static void main(String[] args) {
        List<String> mockList = Mockito.mock(List.class);
        mockList.size();
        mockList.size();
        Mockito.verify(mockList, Mockito.times(2)).size();
      }
    
    }
    

    如果方法没有被调用2次,就会抛出异常。

    总结

    Mockito基本原理就是对接口或具体类创建动态代理对象,在实际进行方法调用时,会查找是否已经配置了结果,没有就使用默认结果。

    参考

    Mockito官网
    一文让你快速上手 Mockito 单元测试框架
    手把手教你 Mockito 的使用

  • 相关阅读:
    python的包和模块
    python 匿名函数
    hdu 1455 Sticks
    python 返回函数
    python 自定义排序函数
    batchsize对收敛速度的影响
    mini_batch GD
    dropout
    sift
    hog
  • 原文地址:https://www.cnblogs.com/strongmore/p/16271972.html
Copyright © 2020-2023  润新知