• Mockito学习(1) --- 快速入门


    Mockito是一个模拟测试框架,可以让你用优雅,简洁的接口写出漂亮的单元测试。Mockito可以让单元测试易于可读,产生简洁的校验错误。

    1、如何使用Mockito

    引入mavne依赖

    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-core</artifactId>
      <version>2.23.4</version>
      <scope>test</scope>
    </dependency>
    
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <scope>test</scope>
    </dependency>
    

    有三种方法可以配置使用 Mockito

    MockitoJUnitRunner 注解

    接下来主要用这种方法来介绍

    import org.junit.runner.RunWith;
    import org.mockito.junit.MockitoJUnitRunner;
    import org.mockito.Mock;
    
    // 测试类加上 @RunWith(MockitoJUnitRunner.class) 注解
    @RunWith(MockitoJUnitRunner.class)
    public class MockByRunnerTest {
        @Mock
        private AccountDao accountDao;
    }
    

    MockitoAnnotations 方式

    import org.mockito.MockitoAnnotations;
    import org.mockito.Mock;
    import org.junit.Before;
    
    public class MockByAnnotationTest {
        @Mock
        private AccountDao accountDao;
        
        @Before
        public void init(){
          	// 初始化
            MockitoAnnotations.initMocks(this);
        }
    }
    
    

    @Rule 注解

    import org.junit.Rule;
    import org.mockito.Mock;
    
    public class MockByRuleTest {
      	// 初始化
        @Rule
        public MockitoRule mockitoRule = MockitoJUnit.rule();
        
        @Mock
        AccountDao accountDao;
    }
    

    2、什么是Mock测试

    mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。

    比如一个类A,有B、C、D等多个复杂类的成员变量。如果我们测试时候通过new A()的方式来创建A对象,就需要同时手动创建B、C、D三个对象,并和A关联,提高了测试的复杂性。Mock可以自动生成一个虚拟的A对象,就是帮我们省去了这些复杂的创建流程。

    Mockito提供了两种Mock的方式:

    import org.mockito.Mock;
    import static org.mockito.Mockito.mock;
    
    @RunWith(MockitoJUnitRunner.class)
    public class MockByRunnerTest2 {
        // 使用@Mock注解
        @Mock
        private AccountDao accountDao;
        
        @Test
        public void test1() {
            Account account = accountDao.findById(1);
          	// 返回null
            System.out.println(account;
            // 输出 class com.sanyue.learn.mockito.mockitodemo.domain.Account$MockitoMock$1675715420
            System.out.println(account.getClass());          
        }
        
        public void test2() {
            // 使用mock方法
            AccountDao accountDao = mock(AccountDao.class, Mockito.RETURNS_SMART_NULLS);
            Account account = accountDao.findById(1);
            // 返回null
          	System.out.println(accoun;
        }
    }
    

    Mock生成的是一个代理对象,默认情况下,执行对象的所有的方法都返回该方法的返回类型的默认值,不会真正去执行该对象的方法。既然这样,那我们在测试中如何使用这个mock出来的对象,来执行方法进行测试呢?这就需要使用到Mockito的Stub(测试桩)来设置Mock对象方法的返回值了。

    3、Stub(测试桩) 介绍

    上面介绍了Mock生成的对象,其实是一个代理对象,不会真正去执行类里面的方法。为了便于我们测试,我们需要用到Stub来设置我们期望的方法返回值,可以理解为创建测试用例

    // 你可以mock具体的类型,不仅只是接口
    LinkedList mockedList = mock(LinkedList.class);
    
    // 开始设置测试桩
    // 当get(0)被调用时,返回"first"
    when(mockedList.get(0)).thenReturn("first");
    
    // 方法get(1)被调用时,抛异常。
    when(mockedList.get(1)).thenThrow(new RuntimeException());
    
    // 输出 "first"
    System.out.println(mockedList.get(0));
    
    // 抛出异常
    System.out.println(mockedList.get(1));
    
    // 输出 null,因为get(999)的调用没有被设置过
    System.out.println(mockedList.get(999));
    

    由上面例子可以看到,Stub就是人为指定 当使用该参数调用该方法时,方法返回什么值。下面介绍一些其他的Stud方式:

     // 使用doReturn语句和when语句一样的效果
     doReturn(1).when(mockedList).get(1);
     // 输出 1
     System.out.println(mockedList.get(1));
    
     // 使用doNothing来设置void返回值的方法
     doNothing().when(mockedList).clear();
     // 设置执行clear方法抛出异常
     doThrow(new RuntimeException()).when(mockedList).clear();
     mockedList.clear();
     // 以下断言表示,mockedList的clear方法被调用了1次
     verify(mockedList, times(1)).clear();
    

    设置每次调用返回不同的值

    如果希望每次调用的返回值都不一样可以这样设置:

    // 第1次调用返回2,第2次返回2,以后再调用返回3
    when(mockedList.size()).thenReturn(1, 2, 3);
    // 等价写法
    // when(mockedList.size()).thenReturn(1).thenReturn(2).thenReturn(3).thenReturn(4);
    // 1
    System.out.println(mockedList.size());
    // 2
    System.out.println(mockedList.size());
    // 3
    System.out.println(mockedList.size());
    // 超过3次后调用,也返回3
    System.out.println(mockedList.size());
    

    也可以通过thenAnswer方式来设置不同调用次数返回不同的值:

    // 设置返回值是 参数值*10
    when(list.get(anyInt())).thenAnswer(new Answer(){
        @Override
        public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
          int arguments = invocationOnMock.getArgument(0);
          return 10*arguments;
        }
    });
    

    参数匹配器

    设置用参数匹配器根据不同类型参数,返回不同的值:

     public class TestService {
       // 定义一个方法
       public String say(String param1, Integer param2, String param3) {
         return "hello";
       }
     }
    
    @Test
    public void test3(){
      TestService testService = mock(TestService.class);
    
      // anyString() 表示任何字符串参数,anyInt() 表示任何int类型参数
      when(testService.say(anyString(), anyInt(), anyString())).thenReturn("world");
      // 输出 world
      System.out.println(testService.say("x", 1, "x"));
      // 如果参数列表包含参数匹配器,则必能出现具体参数值,要使用eq() 方法代替
      // when(testService.say(anyString(), 1, anyString())).thenReturn("world2");
      when(testService.say(anyString(), eq(1), anyString())).thenReturn("world2");
      // 输出 world2
      System.out.println(testService.say("x", 1, "x"));
    }
    

    设置执行真实的方法

    可以使用thenCallRealMethod来设置执行对象真正的方法

     List list = mock(LinkedList.class);
     when(list.size()).thenCallRealMethod();
    

    重置Mock对象

    使用reset方法可以重置Mock对象Stub的设置

    List mock = mock(List.class);
    when(mock.size()).thenReturn(10);
    mock.add(1);
    reset(mock);
    

    do系列方法的运用

    当你调用doThrow(), doAnswer(), doNothing(), doReturn() and doCallRealMethod() 这些函数时可以在适当的位置调用when()函数. 当你需要下面这些功能时这是必须的:

    • 测试void函数
    • 在受监控的对象上测试函数
    • 不知一次的测试为同一个函数,在测试过程中改变mock对象的行为,比如为Spy对象进行Stub

    anyObject(), eq()这样的匹配器函数不会返回匹配器。它们会在内部将匹配器记录到一个栈当中,并且返回一个假的值,通常为null。这样的实现是由于被Java编译器强加的静态类型安全。结果就是你不能在验证或者测试桩函数之外使用anyObject(), eq()函数。

    如果一个方法没有被Stub设置,会返回该方法返回类型的默认值,比如int类型返回0,boolean返回false,对象类型返回null。

    需要记住的是 mock对象会覆盖整个被mock的对象,因此没有stub的方法只能返回默认值,并且类的方法不会真正的执行。

    4、Spy 介绍

    Mock出来的对象(代理对象),默认不会去真正执行类的方法。而用Spy声明的对象(真实对象),则会默认执行真正的方法。

    /** 
    * 也可以使用@Spy注解方式初始化spy对象
    * @Spy
    * List<Integer> list = new ArrayList<>();
    **/
    List<Integer> realList = new ArrayList<>();
    List<Integer> list = spy(realList);
    list.add(1);
    list.add(2);
    
    // 分别输出1和2,说明真正执行了add和get方法
    System.out.println(list.get(0));
    System.out.println(list.get(1));
    
    // 进行部分mock
    when(list.isEmpty()).thenReturn(true);
    // 输出true,说明isEmpty方法被mock了
    System.out.println(list.isEmpty());
    // 分别输出1和2,说明get方法不受mock影响
    System.out.println(list.get(0));
    System.out.println(list.get(1));
    

    需要注意的是,如果为Spy出来的对象进行Stub,有时候不能使用when,因为Spy对象调用方法时,会调用真实的方法。比如以下例子:

    List list = new LinkedList();
    List spy = spy(list);
    
    // 不可能 : 因为当调用spy.get(0)时会调用真实对象的get(0)函数,此时会发生IndexOutOfBoundsException异常,因为真实List对象是空的
    when(spy.get(0)).thenReturn("foo");
    System.out.println(spy.get(0));
    
    // 你需要使用doReturn()来打桩
    doReturn("foo").when(spy).get(0);
    System.out.println(spy.get(0));
    

    SpyMock的相同点和区别:

    1. 得到的对象同样可以进行“监管”,即验证和打桩。

    2. 如果不对spy对象的methodA打桩,那么调用spy对象的methodA时,会调用真实方法。

    3. 如果不对mock对象的methodA打桩,将doNothing,且返回默认值(null,0,false)。

    5、断言

    Mockito中断言的使用和Junit的一样,这里举几个例子,不详细描述:

    List<Integer> list = mock(List.class);
            
    // 断言list.get(0)值等于1
    assertThat(list.get(0), equalTo(1));
    
    // 断言大于50
    assertThat(list.get(0), greaterThan(20));
    // 断言小于等于50
    assertThat(list.get(0), lessThanOrEqualTo(50));
    
    // 断言 必须大于20 并且 小于等于50(所有条件成立)
    assertThat(list.get(0), allOf(greaterThan(20), lessThanOrEqualTo(50)));
    // 断言 必须大于20 或 小于等于50(其中至少一个条件成立)
    assertThat(list.get(0), oneOf(greaterThan(20), lessThanOrEqualTo(50)));
    
    // 断言任何条件都成立
    assertThat(list.get(0), anything());
    // 断言等于1
    assertThat(list.get(0), is(1));
    // 断言不等于-1
    assertThat(list.get(0), not(-1));
    // 断言返回的字符串包含1
    assertThat(list.get(0), containsString("1"));
    // 断言返回的字符串以1开头
    assertThat(list.get(0), startsWith("1"));
    // 断言该异常属于RuntimeException
    assertThat(e, instanceOf(RuntimeException.class));
    

    可以这样断言异常

    try {
    	list.clear();
    	// 如果执行到这一步,返回失败
    	fail();
    } catch (Exception e) {
    	assertThat(e, instanceOf(RuntimeException.class));
    }
    

    6、验证函数的调用次数

    Mockito可以对函数的执行过程进行断言,通过断言函数的执行次数,要对方法执行逻辑进行判断。

    List<Integer> mockedList = mock(List.class);
     mockedList.add("once");
     mockedList.add("twice");
     mockedList.add("twice");
     mockedList.add("three times");
     mockedList.add("three times");
     mockedList.add("three times");
    
     // 下面的两个验证函数效果一样,期望mockedList的add("once")方法执行了1次
     verify(mockedList).add("once");
     verify(mockedList, times(1)).add("once");
    
     // 验证具体的执行次数,分别希望是2次和3次
     verify(mockedList, times(2)).add("twice");
     verify(mockedList, times(3)).add("three times");
    
     // 使用never()进行验证,never相当于times(0),即没有执行过
     verify(mockedList, never()).add("never happened");
    
     // 使用atLeast()至少执行次数/atMost()最多执行次数
     verify(mockedList, atLeastOnce()).add("three times");
     verify(mockedList, atLeast(2)).add("five times");
     verify(mockedList, atMost(5)).add("three times");
    

    7、验证方法的执行顺序

    可以使用InOrder来对方法的执行顺序进行验证

     // 进行mock
    List singleMock = mock(List.class);
    singleMock.add("was added first");
    singleMock.add("was added second");
    
    // 为该mock对象创建一个inOrder对象
    InOrder inOrder = inOrder(singleMock);
    
    // 确保add函数首先执行的是add("was added first"),然后才是add("was added second")
    inOrder.verify(singleMock).add("was added first");
    inOrder.verify(singleMock).add("was added second");
    
  • 相关阅读:
    NumPy笔记:运算符(exp,sqrt,square)
    NumPy笔记:常用操作
    php字符操作
    laravel如何实现批量插入
    php中@符号的作用
    laravel如何实现数据的批量插入
    如何在laravel框架中使用阿里云的oss
    laravle如何设置mysql数据表前缀
    thinkphp视图中如何调用并且向控制器传参数
    thinkphp如何隐藏入口文件index.php
  • 原文地址:https://www.cnblogs.com/moongeek/p/13377174.html
Copyright © 2020-2023  润新知