👍mockito测试框架

1.mock简介

1-1.mock的引入

测试驱动的开发(Test Driven Design, TDD)要求我们先写单元测试,再写实现代码。
而类可能有很多依赖,这些依赖的类/对象/资源又有别的依赖,从而形成很深的依赖树。要在单元测试的环境中完整地构建这样的依赖,是一件很困难的事情。

为了测试类A,我们需要Mock B类和C类

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

1-2.特点

  • 提前创建测试,TDD(测试驱动开发)
  • 团队可以并行工作
  • 你可以创建一个验证或者演示程序
  • 为无法访问的资源编写测试
  • Mock可以交给用户
  • 隔离系统

1-3.mock和stub概念

Mock

所谓的mock,即模拟,模仿的意思。Mock 技术的主要作用是使用mock工具模拟一些在应用中不容易构造或者比较复杂的对象,从而把测试目标与测试边界以外的对象隔离开。

Stub

Stub(桩)。单元测试过程中,对于在应用中不容易构造或者比较复杂的对象,用一个虚拟的对象来代替它。stub的方法也会有具体的实现,哪怕简单到只有一个简单的return语句。
从类的实现方式上看,stub有一个显式的类实现,按照stub类的复用层次可以分类

  • 普通类(被多个测试案例复用)
  • 内部类(被同一个测试案例的多个测试方法复用)
  • 内部匿名类(只用于当前测试方法)。

Stub 与 Mock 的区别
Stub 是在单元测试过程中去代替某些对象来提供所需的测试数据,适用于基于状态的(state-based)测试,关注的是输入和输出。而Mock适用于基于交互的(interaction-based)测试,关注的是交互过程,不只是模拟状态,还能够模拟模块或对象的行为逻辑并能验证其正确性,Mock不需要类的显示实现,直接用工具模拟。

1-4.mock框架

目前流行的mock框架

  • EasyMock

    不是本片的重点,自己科普下吧

  • Mockito

    Mockito也是一个开源的mock工具包,和EasyMock不同的时,它不需要录制、播放这些动作,语法上更灵活,可读性更强

  • PowerMock

    下篇讲解

本篇基于test-ng来使用mockito的

本篇的源码地址

代码坐标在:code目录下,testng-mockito子项目中。

2.业务类

pom依赖

<dependencies>
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>6.14.3</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-all</artifactId>
        <version>1.10.19</version>
        <scope>test</scope>
    </dependency>
</dependencies>

实体类

public class Employee {

    private int empno;

    private String ename;

    private Date hiredate;

    private double sal;

    ...省略setter、getter、toString、构造方法...
}

dao

public class EmployeeDao {

    public void addEmp(Employee employee) {
        System.out.println("EmployeeDao--addEmp: " + employee);
    }

    public List<Employee> listEmps() {
        System.out.println("EmployeeDao--list");
        Employee emp = new Employee(1, "沐雨云楼", new Date(), 20000);
        return Collections.singletonList(emp);
    }

    public List<Employee> getByMap(Map<String, Object> paramMap) {
        System.out.println("EmployeeDao--getByMap: " + paramMap);
        Employee emp = new Employee(1, "沐雨云楼", new Date(), 20000);
        return Collections.singletonList(emp);
    }
}

service

public class EmployeeService {

    private EmployeeDao employeeDao;

    public void addEmp(Employee employee) {
        employeeDao.addEmp(employee);
        System.out.println("添加成功");
    }

    public void listEmps() {
        List<Employee> employees = employeeDao.listEmps();
        System.out.println(employees);
    }

    public List<Employee> getByMap(Map<String, Object> paramMap) {
        return employeeDao.getByMap(paramMap);
    }
}

3.简单测试

3-1. 正常打桩

public class ListTest {

    @Test
    public void test1(){
        LinkedList mockedList = Mockito.mock(LinkedList.class);
        Mockito.when(mockedList.get(0)).thenReturn("first");
        // 返回first
        System.out.println(mockedList.get(0));
        // 返回 null
        System.out.println(mockedList.get(2));
    }
}
  • mock方法,可以创建Mock对象
  • when--thenReturn方式,进行打桩

3-2.异常打桩

@Test(expectedExceptions = RuntimeException.class)
public void test2(){
    LinkedList mockedList = Mockito.mock(LinkedList.class);
    Mockito.when(mockedList.get(0)).thenReturn("first");
    Mockito.when(mockedList.get(1)).thenThrow(new RuntimeException());
    // 返回first
    System.out.println(mockedList.get(0));
    // 返回RuntimeException
    System.out.println(mockedList.get(1));
}
  • when--thenReturn方式,进行异常打桩

3-3.mock参数

@Test
public void test3(){
    LinkedList mockedList = Mockito.mock(LinkedList.class);
    // Mockito.anyInt()
    Mockito.when(mockedList.get(Mockito.anyInt())).thenReturn("first");
    // 返回first
    System.out.println(mockedList.get(0));
    // 返回first
    System.out.println(mockedList.get(1));
}

使用Mockito.anyInt()来定义参数值。除了anyInt()外,还有anyString(),anyMap()

3-4.多个返回值

@Test
public void test4(){
    LinkedList mockedList = Mockito.mock(LinkedList.class);
    Mockito.when(mockedList.get(Mockito.anyInt())).thenReturn("first").thenReturn("second");
    // first
    System.out.println(mockedList.get(0));
    // second
    System.out.println(mockedList.get(1));
    // 后面都返回最后一个值 second
    System.out.println(mockedList.get(2));
}

当有多个返回值时,注意返回值

3-5.doXxx打桩

 @Test
public void test5(){
    LinkedList mockedList = Mockito.mock(LinkedList.class);
    Mockito.doReturn("first").doReturn("second").when(mockedList).get(Mockito.anyInt());
    // first
    System.out.println(mockedList.get(0));
    // second
    System.out.println(mockedList.get(1));
    // 后面都返回最后一个值 second
    System.out.println(mockedList.get(2));
}

使用doReturn--when--method进行打桩

当多个返回值时

@Test
public void test6(){
    LinkedList mockedList = Mockito.mock(LinkedList.class);
    Mockito.doReturn("first").doReturn("second").when(mockedList).get(0);
    // first
    System.out.println(mockedList.get(0));
    // second
    System.out.println(mockedList.get(0));
    // 后面都返回最后一个值 second
    System.out.println(mockedList.get(0));
    // null
    System.out.println(mockedList.get(2));
}

void返回值

@Test
public void test7(){
    LinkedList mockedList = Mockito.mock(LinkedList.class);
    // void 打桩
    Mockito.doNothing().when(mockedList).push(0);
    // void
    mockedList.push(0);
    // first
    System.out.println(mockedList.get(0));
}

使用doNothing来打桩方法返回值为void的方法

3-6.verify

无序校验

@Test
public void test8(){
    LinkedList mockedList = Mockito.mock(LinkedList.class);
    // 操作
    mockedList.get(0);
    mockedList.clear();
    // 验证之前的操作, 无顺序校验
    Mockito.verify(mockedList).clear();
    Mockito.verify(mockedList).get(0);
}

使用verify对方法进行前面mock对象,一些操作校验

次数校验

@Test
public void test10() {
    LinkedList mockedList = Mockito.mock(LinkedList.class);
    // 操作
    mockedList.get(0);
    mockedList.get(1);
    mockedList.get(1);
    mockedList.clear();
    // 验证之前的操作, 有顺序校验 (交换下面顺序,校验不过)
    // 至少一次
    Mockito.verify(mockedList, Mockito.atLeastOnce()).get(0);
    // 指定至少一次参数
    // inOrder.verify(mockedList,Mockito.atLeast(1)).get(0);
    // 没有
    Mockito.verify(mockedList, Mockito.never()).get(100);
    // 最多三次
    Mockito.verify(mockedList, Mockito.atMost(3)).get(1);

    Mockito.verify(mockedList).clear();
}

有序校验

@Test
public void test9(){
    LinkedList mockedList = Mockito.mock(LinkedList.class);
    // 操作
    mockedList.get(0);
    mockedList.clear();
    InOrder inOrder = Mockito.inOrder(mockedList);
    // 验证之前的操作, 有顺序校验 (交换下面顺序,校验不过)
    inOrder.verify(mockedList).get(0);
    inOrder.verify(mockedList).clear();
}

4.注解测试

  • @Captor: 简化 ArgumentCaptor 的创建 - 当需要捕获的参数是一个令人讨厌的通用类,而且你想避免编译时警告。
  • @Spy: 你可以用它代替spy(Object)方法
  • @InjectMocks: 自动将模拟对象或侦查域注入到被测试对象中。

所有新的注解仅仅在MockitoAnnotations.initMocks(Object)方法中被处理

注意

@InjectMocks 也能与 @Spy 一起使用,这就意味着Mockito会注入模拟对象到测试的部分测试中。它的复杂度也是应该使用部分测试原因。

4-1.Mock

之前是通过Mockito.mock方法创建的mock对象。还可以通过自动注解方式来创建mock对象

public class AnnoListTest {

    @Mock
    private LinkedList mockedList;

    @BeforeClass
    public void init() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void test1() {
        Mockito.doReturn("first").when(mockedList).get(Mockito.anyInt());
        Assert.assertEquals("first", mockedList.get(0));
    }
}
  • @Mock: 定义需要mock的对象
  • MockitoAnnotations.initMocks(this): 对当前对象进行mock初始化

4-2.InjectMocks

public class EmployeeServiceTest {

    @InjectMocks
    private EmployeeService employeeService;

    @Mock
    private EmployeeDao employeeDao;

    @BeforeClass
    private void init(){
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testListEmps2() {
        List<Employee> empList =new ArrayList<>();
        empList.add(new Employee(3,"亚瑟",null,2));
        empList.add(new Employee(4,"安其拉",null,3));
        Mockito.doReturn(empList).when(employeeDao).listEmps();
        employeeService.listEmps();
    }
}
  • @InjectMocks: 指定当前要测试的对象
  • @Mock: 指定当前要mock的对象

5.高级测试

5-1.doAnswer

平常使用thenReturn(),thenThrow()打桩就能满足测试。除此外,mockito还支持泛型接口Answer打桩。

public class AnswerTest {

    @Mock
    private EmployeeDao employeeDao;

    @InjectMocks
    private EmployeeService employeeService;

    @BeforeClass
    public void init() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void test1() {
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("name", "海底小纵队");
        List<Employee> empList = new ArrayList<>(1);
        empList.add(new Employee(5, "呱唧", null, 2));
        empList.add(new Employee(6, "巴克队长", null, 3));

        Mockito.doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                Object[] args = invocationOnMock.getArguments();
                System.out.println("调用参数:" + Arrays.toString(args));
                Method method = invocationOnMock.getMethod();
                System.out.println("调用方法:" + method.getName());
                Object mock = invocationOnMock.getMock();
                System.out.println("调用对象类型:" + mock.getClass().getSimpleName());
                return empList;
            }
        }).when(employeeDao).getByMap(paramMap);

        List<Employee> result = employeeService.getByMap(paramMap);
        System.out.println(result);
    }
}

5-2.spy

注意结论

  • mock的对象的方法调用,不会调用真实对象的。
  • spy的对象方法调用,会调用真实对象的。

mock测试

@Test
public void test1() {
    List mock = Mockito.mock(LinkedList.class);
    // 打桩
    Mockito.when(mock.size()).thenReturn(100);
    // 调用的是mock,不会往对象里添加元素
    mock.add("one");
    mock.add("two");
    // null
    System.out.println(mock.get(0));
    // 100, 前面打桩了
    System.out.println(mock.size());
    // 验证
    Mockito.verify(mock).add("one");
    Mockito.verify(mock).add("two");
}

spy测试

@Test
public void test2() {
    List list = new LinkedList();
    List spy = Mockito.spy(list);
    // 打桩
    Mockito.when(spy.size()).thenReturn(100);
    // 调用对象的真正的方法
    spy.add("one");
    spy.add("two");
    // one
    System.out.println(spy.get(0));
    // 100, 前面打桩了
    System.out.println(spy.size());
    // 验证
    Mockito.verify(spy).add("one");
    Mockito.verify(spy).add("two");
}

常用的暂时就介绍这么多,更多的可以参照官网。

6.扩展链接


转载请注明来源,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 157162006@qq.com

文章标题:👍mockito测试框架

字数:2.4k

本文作者:沐雨云楼

发布时间:2020-08-23, 10:45:17

最后更新:2020-09-12, 21:21:47

原始链接:https://iworkh.gitee.io/blog/2020/08/23/java-mockito/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏

pgmanor iworkh gitee