Java开发之模拟框架Mockito

日常的开发中,习惯性地写完需求代码后,嗖的一声运行一个main函数或写几个简单的JUnit的单元测试来跑功能点,java培训多写几个单元测试过没有问题就可以上线了(其实这样是不规范的),对于需要对接第三方或者验证不同条件的代码分支逻辑时,这种方法就会变得不可取,因为业务逻辑中需要依赖其他的接口,而这时候所依赖的接口还没有准备好,那我们应该怎么办呢?

这时候该Mockito派上用场了,一方面使用Mockito可以屏蔽依赖接口并返回Mock数据,使得双方的开发得以同步进行(确定接口的协议)编码,另一方面使用Mockito验证业务逻辑,当日后更改到某处代码即可回归测试用例看改动是否覆盖到所有的测试点,因此使用Mockito不单单能保证代码的质量,更能提高代码维护性、提前发现代码的bug。

 

Mock四要素

  • 什么是Mock

在软件开发的世界之外, "Mock"一词是指模仿或者效仿。 因此可以将“Mock”理解为一个替身、替代者,在软件开发中提及"Mock",通常理解为模拟对象或者Fake

  • 为什么需要Mock

Mock是为了解决units、代码分层开发之间由于耦合而难于被测试的问题,所以mock object是单元测试的一部分

  • Mock的好处是什么

提前创建测试,提高代码质量、TDD(测试驱动开发)

  • 并行工作

创建一个验证或者演示程序,为无法访问的资源编写测试

什么是Mockito

Mockito是一个非常优秀的模拟框架,可以使用它简洁的API来编写漂亮的测试代码,它的测试代码可读性高同时会产生清晰的错误日志。

1、引入依赖:

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

注意:Mockito 3.X的版本使用了JDK8的API,但与2.X的版本并没有太大的变化。

2、在测试类中添加@RunWith注解,并制定Runner的类,即MockitoJUnitRunner

@RunWith(MockitoJUnitRunner.class)
public class MockitoDemoTest {
    
    //注入依赖的资源对象
    @Mock
    private MockitoTestService mockitoTestService;
    @Before
    public void before(){
        MockitoAnnotations.initMocks(this);
    }
}

从代码中可观察到,使用@Mock标识对象是被Mock的,同时在初始化前置执行MockitoAnnotations.initMocks(this)告诉框架生Mock相关注解生效。

3、验证对象的行为Verify

@Test
public void testVerify(){
    //创建mock
    List mockedList = mock(List.class);
    mockedList.add("1");
    mockedList.clear();
    //验证list调用过add的操作行为
    verify(mockedList).add("1");
    //验证list调用过clear的操作行为
    verify(mockedList).clear();
    //使用内建anyInt()参数匹配器,并存根
    when(mockedList.get(anyInt())).thenReturn("element");
    System.out.println(mockedList.get(2)); //此处输出为element
    verify(mockedList).get(anyInt());
}

4、存根—stubbing

stubbing完全是模拟一个外部依赖,用来提供测试时所需要的测试数据

@Test
public void testStub(){
    //可以mock具体的类,而不仅仅是接口
    LinkedList mockedList = mock(LinkedList.class);
    //存根(stubbing)
    when(mockedList.get(0)).thenReturn("first");
    when(mockedList.get(1)).thenThrow(new RuntimeException());
    //下面会打印 "first"
    System.out.println(mockedList.get(0));
    //下面会抛出运行时异常
    System.out.println(mockedList.get(1));
    //下面会打印"null" 因为get(999)没有存根(stub)
    System.out.println(mockedList.get(999));
    doThrow(new RuntimeException()).when(mockedList).clear();
    //下面会抛出 RuntimeException:
    mockedList.clear();
}
  • 存根(stub)可以覆盖:例如测试方法可以覆盖通用存
  • 一旦做了存根方法将总是返回存根的值,无论这个方法被调用多少次

5、存根的连续调用

@Test
public void testStub() {
    when(mock.someMethod("some arg"))
    .thenThrow(new RuntimeException())
    .thenReturn("foo");
    mock.someMethod("some arg"); //第一次调用:抛出运行时异常
    //第二次调用: 打印 "foo"
    System.out.println(mock.someMethod("some arg"));
    //任何连续调用: 还是打印 "foo" (最后的存根生效).
    System.out.println(mock.someMethod("some arg"));
    //可供选择的连续存根的更短版本:
    when(mock.someMethod("some arg")).thenReturn("one", "two", "three");
    when(mock.someMethod(anyString())).thenAnswer(new Answer() {
        Object answer(InvocationOnMock invocation) {
            Object[] args = invocation.getArguments();
            Object mock = invocation.getMock();
            return "called with arguments: " + args;
        }
    });
    // "called with arguments: foo
    System.out.println(mock.someMethod("foo"));
}

6、参数匹配器

@Test
public void testArugument{
    //使用内建anyInt()参数匹配器
    when(mockedList.get(anyInt())).thenReturn("element");
    System.out.println(mockedList.get(999)); //打印 "element"
    //同样可以用参数匹配器做验证
    verify(mockedList).get(anyInt());


    //注意:如果使用参数匹配器,所有的参数都必须通过匹配器提供。
    verify(mock)
    .someMethod(anyInt(), anyString(), eq("third argument"));
    //上面是正确的 - eq(0也是参数匹配器),而下面的是错误的
    verify(mock)
    .someMethod(anyInt(), anyString(), "third argument");
}

7、验证精确调用次数/至少X次/从不

@Test
public void testVerify{
    List<String> mockedList = new ArrayList();
    mockedList.add("once");
    mockedList.add("twice");
    mockedList.add("twice");
    mockedList.add("three times");
    mockedList.add("three times");
    mockedList.add("three times");
    //下面两个验证是等同的 - 默认使用times(1)
    verify(mockedList).add("once");
    verify(mockedList, times(1)).add("once");
    verify(mockedList, times(2)).add("twice");
    verify(mockedList, times(3)).add("three times");
    //使用using 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");
}

8、验证调用顺序

@Test
public void testOrder(){
    // A. 单个Mock,方法必须以特定顺序调用
    List singleMock = mock(List.class);


    //使用单个Mock
    singleMock.add("was added first");
    singleMock.add("was added second");


    //为singleMock创建 inOrder 检验器
    InOrder inOrder = inOrder(singleMock);


    //确保add方法第一次调用是用"was added first",然后是用"was added second"
    inOrder.verify(singleMock).add("was added first");
    inOrder.verify(singleMock).add("was added second");
}

来源作者:GoQeng

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值