zoukankan      html  css  js  c++  java
  • 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");
    
  • 相关阅读:
    Eclipse / android studio 添加第三方jar包 步骤
    Android checkbox 自定义点击效果
    Android 程序打包和安装过程
    Android 基础
    (转)Genymotion安装virtual device的“unable to create virtual device, Server returned Http status code 0”的解决方法
    (转)eclipse 导入Android 项目 步骤
    微信开放平台注册 步骤
    Android Studio 初级安装
    数组
    作用域问题代码
  • 原文地址:https://www.cnblogs.com/moongeek/p/13377174.html
Copyright © 2011-2022 走看看