什么是 Mock 测试

Mock 测试就是在测试过程中,对于某些不容易构造(如 HttpServletRequest 必须在Servlet 容器中才能构造出来)或者不容易获取比较复杂的对象(如 JDBC 中的ResultSet 对象),用一个虚拟的对象(Mock 对象)来创建以便测试的测试方法。
Mock 最大的功能是帮你把单元测试的耦合分解开,如果你的代码对另一个类或者接口有依赖,它能够帮你模拟这些依赖,并帮你验证所调用的依赖的行为。
比如一段代码有这样的依赖:
Mybatis之Mockito单元测试 - 图1
当我们需要测试A类的时候,如果没有 Mock,则我们需要把整个依赖树都构建出来,而使用 Mock 的话就可以将结构分解开,像下面这样:
Mybatis之Mockito单元测试 - 图2

Mock 对象使用范畴

真实对象具有不可确定的行为,产生不可预测的效果(如:股票行情,天气预报) :

  • 真实对象很难被创建的
  • 真实对象的某些行为很难被触发
  • 真实对象实际上还不存在的(和其他开发小组或者和新的硬件打交道)等等

    使用 Mock 对象测试的关键步骤

  • 使用一个接口来描述这个对象

  • 在产品代码中实现这个接口
  • 在测试代码中实现这个接口
  • 在被测试代码中只是通过接口来引用对象,所以它不知道这个引用的对象是真实对象,还是 Mock 对象。

    Mock 与 Stub 的区别

    Mock 不是 Stub,两者是有区别的:

  • 前者被称为 mockist TDD,而后者一般称为 classic TDD ;

  • 前者是基于行为的验证(behavior verification),后者是基于状态的验证 (state verification);
  • 前者使用的是模拟的对象,而后者使用的是真实的对象。

    Java Mock 测试

    目前,在 Java 阵营中主要的 Mock 测试工具有 MockitoJMockEasyMock 等。
    关于这些框架的比较,不是本文的重点。本文着重介绍 Mockito 的使用。

    Mockito 的特性

    Mockito 是美味的 Java 单元测试 Mock 框架,开源
    大多 Java Mock 库如 EasyMock 或 JMock 都是 expect-run-verify (期望-运行-验证)方式,而 Mockito 则使用更简单,更直观的方法:在执行后的互动中提问。使用 Mockito,你可以验证任何你想要的。而那些使用 expect-run-verify 方式的库,你常常被迫查看无关的交互。
    非 expect-run-verify 方式 也意味着,Mockito 无需准备昂贵的前期启动。他们的目标是透明的,让开发人员专注于测试选定的行为。
    Mockito 拥有的非常少的 API,所有开始使用 Mockito,几乎没有时间成本。因为只有一种创造 mock 的方式。只要记住,在执行前 stub,而后在交互中验证。你很快就会发现这样 TDD java 代码是多么自然。
    类似 EasyMock 的语法来的,所以你可以放心地重构。Mockito 并不需要“expectation(期望)”的概念。只有 stub 和验证。
    Mockito 实现了 Gerard Meszaros 所谓的 Test Spy.

    其他的一些特点:

  • 可以 mock 具体类而不单止是接口

  • 一点注解语法糖 - @Mock
  • 干净的验证错误是 - 点击堆栈跟踪,看看在测试中的失败验证;点击异常的原因来导航到代码中的实际互动。堆栈跟踪总是干干净净。
  • 允许灵活有序的验证(例如:你任意有序 verify,而不是每一个单独的交互)
  • 支持“详细的用户号码的时间”以及“至少一次”验证
  • 灵活的验证或使用参数匹配器的 stub (anyObject(),anyString() 或 refEq() 用于基于反射的相等匹配)
  • 允许创建自定义的参数匹配器或者使用现有的 hamcrest 匹配器

    IDEA 中如何使用Mockito框架

    Mockito 语法流程

    1. Mockito
    2. Mock
    3. mock(Class classToMock);
    4. mock(Class classToMock, String name)
    5. Stub
    6. when(mock.someMethod()).thenReturn(value)
    7. when(mock.someMethod()).thenThrow(new RuntimeException)
    8. when(mock.someMethod()).thenAnswer()
    9. exec

    首先要利用mock来构造依赖,其次利用when语句来构造stub,然后就可以执行测试方法了
    如何使用Mockito

  1. 在Intellij IDEA中创建Maven项目
  2. 在pom.xml添加依赖包
    1. <dependency>
    2. <groupId>org.mockito</groupId>
    3. <artifactId>mockito-core</artifactId>
    4. <version>2.15.0</version>
    5. <scope>test</scope>
    6. </dependency>
    7. <dependency>
    8. <groupId>junit</groupId>
    9. <artifactId>junit</artifactId>
    10. <version>4.12</version>
    11. </dependency>
    接下来就是创建测试类在src/test/java 目录里 ```java package com.portal.mock;

import org.testng.annotations.Test;

import java.util.LinkedList; import java.util.List;

import static org.mockito.Mockito.*;

public class MockTest {

  1. @Test
  2. public void testA(){
  3. //You can mock concrete classes, not just interfaces
  4. LinkedList mockList=mock(LinkedList.class);
  5. //using mock
  6. mockList.add("once");
  7. mockList.add("twice");
  8. mockList.add("twice");
  9. mockList.add("three times");
  10. mockList.add("three times");
  11. mockList.add("three times");
  12. //following two verifications work exactly the same - times(1) is used by default
  13. verify(mockList).add("once");
  14. verify(mockList,times(1)).add("once");
  15. //exact number of invocations verification
  16. verify(mockList, times(2)).add("twice");
  17. verify(mockList,times(3)).add("three times");
  18. //verification using never(). never() is an alias to times(0)
  19. verify(mockList,never()).add("never happend");
  20. //verification using atLeast()/atMost()
  21. verify(mockList,atLeastOnce()).add("three times");
  22. verify(mockList,atLeast(2)).add("three times");
  23. verify(mockList,atMost(5)).add("three times");
  24. }
  25. @Test
  26. public void testB(){
  27. List list = new LinkedList();
  28. List spy = spy(list);
  29. //optionally, you can stub out some methods:
  30. when(spy.size()).thenReturn(100);
  31. //using the spy calls *real* methods
  32. spy.add("zero"); //index从0开始
  33. spy.add("one");
  34. spy.add(2,"three"); //指定index
  35. //prints "one" - the first element of a list
  36. System.out.println("spy.index0: "+spy.get(0));
  37. System.out.println("spy.index1: "+spy.get(1));
  38. System.out.println("spy.index2: "+spy.get(2));
  39. //size() method was stubbed - 100 is printed
  40. System.out.println("Spy.size: "+ spy.size());
  41. //optionally, you can verify
  42. verify(spy).add("zero");
  43. verify(spy).add("one");
  44. verify(spy).add(2,"three");
  45. }

}

  1. 看下执行结果:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22478710/1632674498491-24ac9933-22e8-4593-a5ab-06ddf04cb4ee.png#clientId=ue434ae4b-7da6-4&from=paste&id=u66148639&margin=%5Bobject%20Object%5D&name=image.png&originHeight=416&originWidth=905&originalType=url&ratio=1&size=177365&status=done&style=none&taskId=ua1ada28e-d157-4775-855e-8eb79fd210d)<br />我们把验证结果修改下,看会发生什么
  2. ```java
  3. @Test
  4. public void testB(){
  5. List list = new LinkedList();
  6. List spy = spy(list);
  7. //optionally, you can stub out some methods:
  8. when(spy.size()).thenReturn(100);
  9. //using the spy calls *real* methods
  10. spy.add("zero"); //默认index从0开始
  11. spy.add("one");
  12. spy.add(2,"three"); //指定index
  13. //prints "one" - the first element of a list
  14. System.out.println("spy.index0: "+spy.get(0));
  15. System.out.println("spy.index1: "+spy.get(1));
  16. System.out.println("spy.index2: "+spy.get(2));
  17. //size() method was stubbed - 100 is printed
  18. System.out.println("Spy.size: "+ spy.size());
  19. //optionally, you can verify
  20. verify(spy).add("zero");
  21. verify(spy).add("one");
  22. verify(spy).add(2,"two"); //修改验证结果 three -> two
  23. }

image.png
Mockito语法相对来说还是比较简练的,学习成本并不算太高.
如果你有好的工具或者框架欢迎推荐给我, 一起学习一起进步.
总结:
测试是一门技术, 更是一门艺术. 也许你今天拥有的技术, 明天就会被淘汰. 同时需要我们开拓思维和眼界, 积极拥抱变化, 学习新知识, 新方法,新技能, 计算机领域讲究的是实践, 学习更要讲究方式方法. 学习和动手一定要结合, 光看不练,犹如看武功秘籍, 是永远成不了武功大侠的.

原文链接:https://cloud.tencent.com/developer/article/1604173