单元测试利器——手把手教你使用Mockito(一)

发表于:2023-3-23 09:23

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:秦浩然    来源:知乎

  从你成为开发人员的那一天起,写单元测试终究是你逃不开的宿命!那开发人员为什么不喜欢写单元测试呢?究其原因,无外乎是依赖。依赖其他的服务、依赖运行的环境、等等,各种依赖都成为了我们写单元测试的绊脚石。那现在有个单元测试利器可以帮我们解决依赖的问题,你愿意使用一下吗?你愿意!那就是我们要学习的 Mockito
  一、前期准备
  1、准备工作
  <!--mockito依赖-->
  <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-core</artifactId>
      <version>2.7.19</version>
      <scope>test</scope>
  </dependency>
  <!-- junit依赖 -->
  <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
  </dependency>
  2、入门知识
  1)Mockito:简单轻量级的做mocking测试的框架;
  2)mock对象:在调试期间用来作为真实对象的替代品;
  3)mock测试:在测试过程中,对那些不容易构建的对象用一个虚拟对象来代替测试的方法就叫mock测试;
  4)stub:打桩,就是为mock对象的方法指定返回值(可抛出异常);
  5)verify:行为验证,验证指定方法调用情况(是否被调用,调用次数等);
  3、五分钟入门 Demo
  @Test
  public void test0() {
      //1、创建mock对象(模拟依赖的对象)
      final List mock = Mockito.mock(List.class);
      //2、使用mock对象(mock对象会对接口或类的方法给出默认实现)
      System.out.println("mock.add result => " + mock.add("first"));  //false
      System.out.println("mock.size result => " + mock.size());       //0
      //3、打桩操作(状态测试:设置该对象指定方法被调用时的返回值)
      Mockito.when(mock.get(0)).thenReturn("second");
      Mockito.doReturn(66).when(mock).size();
      //3、使用mock对象的stub(测试打桩结果)
      System.out.println("mock.get result => " + mock.get(0));    //second
      System.out.println("mock.size result => " + mock.size());   //66
      //4、验证交互 verification(行为测试:验证方法调用情况)
      Mockito.verify(mock).get(Mockito.anyInt());
      Mockito.verify(mock, Mockito.times(2)).size();
      //5、验证返回的结果(这是JUnit的功能)
      assertEquals("second", mock.get(0));
      assertEquals(66, mock.size());
  }
  二、让我们开始学习吧!
  1、行为验证
  一旦 mock 对象被创建了,mock 对象会记住所有的交互,然后你就可以选择性的验证你感兴趣的交互,验证不通过则抛出异常。
  @Test
  public void test1() {
      final List mockList = Mockito.mock(List.class);
      mockList.add("mock1");
      mockList.get(0);
      mockList.size();
      mockList.clear();
      // 验证方法被使用(默认1次)
      Mockito.verify(mockList).add("mock1");
      // 验证方法被使用1次
      Mockito.verify(mockList, Mockito.times(1)).get(0);
      // 验证方法至少被使用1次
      Mockito.verify(mockList, Mockito.atLeast(1)).size();
      // 验证方法没有被使用
      Mockito.verify(mockList, Mockito.never()).contains("mock2");
      // 验证方法至多被使用5次
      Mockito.verify(mockList, Mockito.atMost(5)).clear();
      // 指定方法调用超时时间
      Mockito.verify(mockList, timeout(100)).get(0);
      // 指定时间内需要完成的次数
      Mockito.verify(mockList, timeout(200).atLeastOnce()).size();
  }
  2、如何做一些测试桩 stub
  默认情况下,所有的函数都有返回值。mock 函数默认返回的是 null,一个空的集合或者一个被对象类型包装的内置类型,例如 0、false 对应的对象类型为 Integer、Boolean;
  一旦测试桩函数被调用,该函数将会一致返回固定的值;
  对于 static 和 final 方法, Mockito 无法对其 when (…).thenReturn (…) 操作。
  @Test
  public void test2() {
      //静态导入,减少代码量:import static org.mockito.Mockito.*;
      final ArrayList mockList = mock(ArrayList.class);
      // 设置方法调用返回值
      when(mockList.add("test2")).thenReturn(true);
      doReturn(true).when(mockList).add("test2");
      System.out.println(mockList.add("test2"));  //true
      // 设置方法调用抛出异常
      when(mockList.get(0)).thenThrow(new RuntimeException());
      doThrow(new RuntimeException()).when(mockList).get(0);
      System.out.println(mockList.get(0));    //throw RuntimeException
      // 无返回方法打桩
      doNothing().when(mockList).clear();
      // 为回调做测试桩(对方法返回进行拦截处理)
      final Answer<String> answer = new Answer<String>() {
          @Override
          public String answer(InvocationOnMock invocationOnMock) throws Throwable {
              final List mock = (List) invocationOnMock.getMock();
              return "mock.size result => " + mock.size();
          }
      };
      when(mockList.get(1)).thenAnswer(answer);
      doAnswer(answer).when(mockList).get(1);
      System.out.println(mockList.get(1));    //mock.size result => 0
      // 对同一方法多次打桩,以最后一次为准
      when(mockList.get(2)).thenReturn("test2_1");
      when(mockList.get(2)).thenReturn("test2_2");
      System.out.println(mockList.get(2));    //test2_2
      System.out.println(mockList.get(2));    //test2_2
      // 设置多次调用同类型结果
      when(mockList.get(3)).thenReturn("test2_1", "test2_2");
      when(mockList.get(3)).thenReturn("test2_1").thenReturn("test2_2");
      System.out.println(mockList.get(3));    //test2_1
      System.out.println(mockList.get(3));    //test2_2
      // 为连续调用做测试桩(为同一个函数调用的不同的返回值或异常做测试桩)
      when(mockList.get(4)).thenReturn("test2").thenThrow(new RuntimeException());
      doReturn("test2").doThrow(new RuntimeException()).when(mockList).get(4);
      System.out.println(mockList.get(4));    //test2
      System.out.println(mockList.get(4));    //throw RuntimeException
      // 无打桩方法,返回默认值
      System.out.println(mockList.get(99));    //null
  }
  3、参数匹配器
  ·参数匹配器使验证和测试桩变得更灵活;
  · 为了合理的使用复杂的参数匹配,使用 equals () 与 anyX () 的匹配器会使得测试代码更简洁、简单。有时,会迫使你重构代码以使用 equals () 匹配或者实现 equals () 函数来帮助你进行测试;
  · 如果你使用参数匹配器,所有参数都必须由匹配器提供;
  · 支持自定义参数匹配器;
  @Test
  public void test3() {
      final Map mockMap = mock(Map.class);
      // 正常打桩测试
      when(mockMap.get("key")).thenReturn("value1");
      System.out.println(mockMap.get("key"));     //value1
      // 为灵活起见,可使用参数匹配器
      when(mockMap.get(anyString())).thenReturn("value2");
      System.out.println(mockMap.get(anyString()));   //value2
      System.out.println(mockMap.get("test_key"));    //value2
      System.out.println(mockMap.get(0)); //null
      // 多个入参时,要么都使用参数匹配器,要么都不使用,否则会异常
      when(mockMap.put(anyString(), anyInt())).thenReturn("value3");
      System.out.println(mockMap.put("key3", 3));     //value3
      System.out.println(mockMap.put(anyString(), anyInt()));     //value3
      System.out.println(mockMap.put("key3", anyInt()));    //异常
      // 行为验证时,也支持使用参数匹配器
      verify(mockMap, atLeastOnce()).get(anyString());
      verify(mockMap).put(anyString(), eq(3));
      // 自定义参数匹配器
      final ArgumentMatcher<ArgumentTestRequest> myArgumentMatcher = new ArgumentMatcher<ArgumentTestRequest>() {
          @Override
          public boolean matches(ArgumentTestRequest request) {
              return "name".equals(request.getName()) || "value".equals(request.getValue());
          }
      };
      // 自定义参数匹配器使用
      final ArgumentTestService mock = mock(ArgumentTestService.class);
      when(mock.argumentTestMethod(argThat(myArgumentMatcher))).thenReturn("success");
      doReturn("success").when(mock).argumentTestMethod(argThat(myArgumentMatcher));
      System.out.println(mock.argumentTestMethod(new ArgumentTestRequest("name", "value")));  // success
      System.out.println(mock.argumentTestMethod(new ArgumentTestRequest()));     //null
  }
  4、执行顺序验证
  ·验证执行顺序是非常灵活的 - 你不需要一个一个的验证所有交互,只需要验证你感兴趣的对象即可;
  · 你可以仅通过那些需要验证顺序的 mock 对象来创建 InOrder 对象;
  @Test
  public void test4() {
      // 验证同一个对象多个方法的执行顺序
      final List mockList = mock(List.class);
      mockList.add("first");
      mockList.add("second");
      final InOrder inOrder = inOrder(mockList);
      inOrder.verify(mockList).add("first");
      inOrder.verify(mockList).add("second");
      // 验证多个对象多个方法的执行顺序
      final List mockList1 = mock(List.class);
      final List mockList2 = mock(List.class);
      mockList1.get(0);
      mockList1.get(1);
      mockList2.get(0);
      mockList1.get(2);
      mockList2.get(1);
      final InOrder inOrder1 = inOrder(mockList1, mockList2);
      inOrder1.verify(mockList1).get(0);
      inOrder1.verify(mockList1).get(2);
      inOrder1.verify(mockList2).get(1);
  }
  5、确保交互(interaction)操作不会执行在 mock 对象上
  一些用户可能会在频繁地使用 verifyNoMoreInteractions (),甚至在每个测试函数中都用。但是 verifyNoMoreInteractions () 并不建议在每个测试函数中都使用;
  verifyNoMoreInteractions () 在交互测试套件中只是一个便利的验证,它的作用是当你需要验证是否存在冗余调用时。
  @Test
  public void test5() {
      // 验证某个交互是否从未被执行
      final List mock = mock(List.class);
      mock.add("first");
      verify(mock, never()).add("test5");   //通过
      verify(mock, never()).add("first");  //异常
      // 验证mock对象没有交互过
      final List mock1 = mock(List.class);
      final List mock2 = mock(List.class);
      verifyZeroInteractions(mock1);  //通过
      verifyNoMoreInteractions(mock1, mock2); //通过
      verifyZeroInteractions(mock, mock2);  //异常
      // 注意:可能只想验证前面的逻辑,但是加上最后一行,会导致出现异常。建议使用方法层面的验证,如:never();
      //      在验证是否有冗余调用的时候,可使用此种方式。如下:
      final List mockList = mock(List.class);
      mockList.add("one");
      mockList.add("two");
      verify(mockList).add("one");    // 通过
      verify(mockList, never()).get(0);    //通过
      verifyZeroInteractions(mockList);   //异常
  }
  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号