什么是Mock?

在面向对象程序设计中,模拟对象(英语:mock object,也译作模仿对象)是以可控的方式模拟真实对象行为的假的对象。程序员通常创造模拟对象(mock object)来测试其他对象的行为,很类似汽车设计者使用碰撞测试假人来模拟车辆碰撞中人的动态行为。

为什么要使用Mock?

在单元测试中,模拟对象可以模拟复杂的、真实的(非模拟)对象的行为, 如果真实的对象无法放入单元测试中,使用模拟对象就很有帮助。

在下面的情形,可能需要使用模拟对象来代替真实对象:

真实对象的行为是不确定的(例如,当前的时间或当前的温度);

真实对象很难搭建起来;

真实对象的行为很难触发(例如,网络错误);

真实对象速度很慢(例如,一个完整的数据库,在测试之前可能需要初始化);

真实的对象是用户界面,或包括用户界面在内;

真实的对象使用了回调机制;

真实对象可能还不存在;

真实对象可能包含不能用作测试(而不是为实际工作)的信息和方法。

例如,一个可能会在特定的时间响铃的闹钟程序可能需要外部世界的当前时间。要测试这一点,测试一直要等到闹铃时间才知道闹钟程序是否正确地响铃。如果使用一个模拟对象替代真实的对象,可以变成提供一个闹铃时间(不管是否实际时间),这样就可以隔离地测试闹钟程序。

Mockito的简单使用

Mockito是GitHub上使用最广泛的Mock框架,并与JUnit(java单元测试框架)结合使用。Mockito框架可以创建和配置mock对象.使用Mockito简化了具有外部依赖的类的测试开发!

一般使用Mockito的步骤:

1、模拟任何外部依赖并将这些模拟对象插入测试代码中

2、执行测试中的代码

3、验证代码是否按照预期执行

单元测试是每个程序员的一项基本技能,甚至于还出现一种 TDD 的敏捷软件设计开发方法。在我们划分好模块进行详细设计编码之前,可能只是粗略的定义了一些接口,在我们进行的前后端分离开发方式实践中,以及微服务架构的系统设计中,经常会遇到这种情况。

在我们需要测试的代码所依赖的服务还未实现,或者说要构建依赖的对象比较困难时,使用Mock的方式进行单元测试是一种比较好的选择。例如我们在使用 Spring 框架开发和测试 Service 层的代码时,并不需要等到 Dao 层的相关代码开发完成才进行单元测试。

本文主要介绍Java编程领域一个非常好用的Mock框架的应用。

1、Mockito的引入

Mockito 目前发布的是 2.x 版本( Mockito 3.x 版本目前还在开发中,会考虑 Java 8 的一些新特性)。我们以 Maven 为例(当然根据自己项目的情况也可以使用 Ivy、Gradle、SBT 等等,甚至直接把 jar 包下载下来放到项目中使用),只需要在 Maven 项目的 pom 文件中增加 Mockito 依赖即可。

<dependency>

<groupId>org.mockito</groupId>

<artifactId>mockito-core</artifactId>

<version>2.23.0</version>

</dependency>

2、第一段Mock测试代码。

考虑一个简单的用户注册功能,我们需要先判断注册用户的用户名是否被其他用户注册过。如果已被注册过,则注册失败,如果未被注册过,则保存注册信息,注册成功。下面是代码设计(示例代码使用 spring 框架,并使用了 lombok 以减少 POJO 类的 getter,setter 定义):

@Data

public class User {

private String idUser;      // 用户ID

private String username;    // 用户名

private String password;    // 用户密码

}

public interface UserService {

/**

* 新用户注册。注册成功返回true,注册失败返回false

* @param user 新注册用户

* @return

*/

boolean regist(User user);

}

@Service

public class UserServiceImpl implements UserService {

@Autowired

private UserDao userDao;

@Override

public boolean regist(User user) {

User existUser = userDao.queryByUsername(user.getUsername());

if (existUser == null) {

userDao.insertUser(user);

return true;

}

return false;

}

}

public interface UserDao {

/**

* 根据用户名查询用户

* @param username 用户名

* @return

*/

User queryByUsername(String username);

/**

* 持久化新用户

* @param user 新用户

* @return

*/

void insertUser(User user);

}

这个时候 UserDao 的实现类还没有开发,我们要测试 UserService 的 regist 方法时就可以使用 Mock 了。下面就是第一段使用 JUnit 结合 Mockito 编写的单元测试代码。

@RunWith(MockitoJUnitRunner.class)

public class UserServiceImplTest {

String existUsername    = "spiderman";

String notExistUsername = "ironman";

@Mock

private UserDao        userDao;

@InjectMocks

private UserServiceImpl userService;

@Before

public void setUp() throws Exception {

// 效果同@RunWith(MockitoJUnitRunner.class)

// MockitoAnnotations.initMocks(this);

User existUser = new User();

existUser.setUsername(existUsername);

existUser.setPassword("aaaaa");

// 当调用userDao.queryByUsername入参为"spiderman"时会返回existUser对象,表示该用户已存在

Mockito.when(userDao.queryByUsername(existUsername)).thenReturn(existUser);

// 当调用userDao.queryByUsername入参为"ironman"时会返回null值,表示不存在该用户

Mockito.when(userDao.queryByUsername(notExistUsername)).thenReturn(null);

}

@Test

public void testExists() throws Exception {

User testUser = new User();

testUser.setUsername(existUsername);

Assert.assertFalse(userService.regist(testUser));

}

@Test

public void testNotExists() throws Exception {

User testUser = new User();

testUser.setUsername(notExistUsername);

Assert.assertTrue(userService.regist(testUser));

}

}

以上单元测试代码除了几行带有 Mock 字样的代码,其他内容和我们之前写的单元测试没有区别。从面的代码我们可以看到 userService 依赖了 userDao ,但是我们的代码中并没有实例化这两个对象,并且源代码中也没有 UserDao 的具体实现,但是我们的测试代码却可以像是已经实例化了这两个对象一样进行操作。下面我们就来看看这几行新增代码的作用。

3、Mock注解介绍。

@RunWith(MockitoJUnitRunner.class) 该注解会在test方法执行之前初始化使用 @Spy & @Mock & @InjectMocks 注解的对象;该注解还会自动验证我们单元测试用Mockito框架的使用情况。如果我们在调用Mockito的静态方法when之后继续链式调用相应的 stub 方法(如上面示例代码中去掉thenReturn方法调用),单元测试代码可以编译通过,是运行时会报错。

我们在setup方法中第一行使用MockitoAnnotations.initMocks(this)可以达到该注解同样的效果。

当然我们也可以使用 Mockito.mock 方法手动创建 mock 对象,但是并不推荐这样做。

@Mock 该注解表示会创建一个mock对象。我们在该 mock 对象上的方法调用并不会实际调用具体的实现代码(也可能其实本来就还没有实现)

@Spy 该注解上面示例代码并未使用,功能与 @Mock 类似,也是创建一个mock对象,区别在于调用 @Spy 对应的mock对象上的方法时,会实际调用事实实现好的代码(如果已有实现方法的前提下),但是不会影响我们when-then语句中的定义。但是使用该注解还是会为我们省去对象创建的过程。例如上面示例中如果我们实现了 UserDao 接口:

@Repository

public class UserDaoImpl implements UserDao {

@Override

public User queryByUsername(String username) {

System.out.println("call UserDaoImpl.queryByUsername");

return null;

}

@Override

public void insertUser(User user) {

}

}

然后更换单元测试类型的 Mock定义:

@Mock

private UserDao userDao;

更换为

@Spy

private UserDaoImpl userDao;

执行单元测试后我们可以在控制台看到 UserDaoImpl 实现方法中的打印语句:

如果我们再把 @Spy 注解切换回  @Mock 注解,可以发现控制台不会打印 UserDaoImpl 实现方法中的打印语句。

@Spy

privateUserDaoImpl userDao;

更换为

@Mock

privateUserDaoImpl userDao;

@InjectMocks 该注解表示会创建一个测试类的实例,并注入依赖的mock对象(@Mock 注解或 @Spy 注解)。

4、Mock的应用介绍

除了Mock的注解,下面我们再来看看使用Mock的表达式。上面示例代码我们展示了when-then表达式的使用。我们使用该语句定义对象方法调用的一些预先约定。

when方法定义方法场景,指定了具体的mock对象,指定了mock对象的某个具体调用方法,指定了该方法的调用参数值(如果不关心具体的参数值内容,可以用Any代替)。

then方法定义了我们约定的方法调用之后需要具体执行的操作,比如返回一个值或者抛出一个异常。

另外我们还可以使用Mock做一些验证。例如我们在Assert的断言方法后面增加一些判断,如果测试用例是注册不存在的用户,我们的业务逻辑中会调用userDao的insertUser方法,这个时候我们可以增加一行Mockito.verify(userDao).insertUser(testUser)。如果我们的 UserServiceImpl 实现中去掉userDao.insertUser(user)调用,测试不会通过,也就提示我们说新注册的用户没有持久化操作需要修复 UserServiceImpl 里的实现逻辑。

Mockito在 stackoverflow 是程序员投票最广泛使用评价最高的一个 Java 编程 Mock 框架。使用该框架编写的单元测试代码美观、清洁、易于理解,并且功能强大。本文只是简单介绍 Mockito 的一些简单的基础知识,一些复杂的高级的功能另文介绍。

作者:金融测试民工
链接:https://www.jianshu.com/p/b8a52260dc22
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Mock和Java单元测试中的Mock框架Mockito介绍相关推荐

  1. Java单元测试实践-11.Mock后Stub Spring的@Component组件

    Java单元测试实践-00.目录(9万多字文档+700多测试示例) https://blog.csdn.net/a82514921/article/details/107969340 1. Sprin ...

  2. 单元测试中使用Mock对象

    单元测试中使用Mock对象 单元测试中使用Mock对象 一.简单的替换 二.Mock 对象 三.测试 Servlet 单元测试中使用Mock对象 一.简单的替换 假设在代码中,你调用你自己的 getT ...

  3. Error —— 单元测试中如何Mock application文件中的数据?

    问题 单元测试的代码中,有从application.properties中读取的变量. 但在单元测试中,无法读取配置文件,变量默认为空,导致单元测试不通过.如何在单元测试中mock配置文件中的变量呢? ...

  4. 使用 Cobertura 和反射机制提高 Java 单元测试中的代码覆盖率

    本文将介绍两种开发实践,用于提高 Java 单元测试中的代码覆盖率.代码覆盖率 = (被测代码 / 代码总数)* 100%.提高被测代码数量或降低代码总数,均可达到提高代码覆盖率的效果.在本文中,您将 ...

  5. Java单元测试实践-06.Mock后Stub静态方法

    Java单元测试实践-00.目录(9万多字文档+700多测试示例) https://blog.csdn.net/a82514921/article/details/107969340 1. Mock后 ...

  6. 【修真院Java小课堂】Tiles框架简单介绍

    大家好,我是IT修真院上海分院第6期的学员,一枚正直纯洁善良的程序员 今天给大家分享一下,Tiles框架简单介绍 Tiles框架简单介绍 背景介绍 什么是Tiles Tiles 是一种JSP布局框架, ...

  7. 单元测试中使用mock最好不要使用easymock而应该使用powermock

    视频参考汪文君powermock视频教程相当的经典 转载于:https://www.cnblogs.com/kebibuluan/p/9223054.html

  8. java JPI中常使用的类介绍即java.lang包下的东西

    java.lang包是java语言的核心,它提供了java中的基础类.包括基本Object类.Class类.String类.基本类型的包装类.基本的数学类等等最基本的类. 下面分别介绍其中比较常用的类 ...

  9. java resultmap_Mybatis中强大的resultMap功能介绍

    前言 在Mybatis中,有一个强大的功能元素resultMap.当我们希望将JDBC ResultSets中的数据,转化为合理的Java对象时,你就能感受到它的非凡之处.正如其官方所述的那样: re ...

最新文章

  1. 从难免的线上bug说起代码的思考
  2. docker 同时停止删除容器 强制删除容器
  3. Laravel+nginx环境配置好后,url加参数提交报404错误
  4. python列表的嵌套_Python中关于列表嵌套列表的处理
  5. 1149 Dangerous Goods Packaging (25 分)
  6. c语言经典例题100例
  7. offes给excel增加下拉选项_财务“救星”:Excel不止可以下拉单元格,还可以进行成本核算...
  8. 课程笔记:深度学习与人类语言处理 ——李宏毅,2020 (P5)
  9. PHP中去除换行解决办法小结(PHP_EOL)
  10. Oracle 数据脱敏
  11. this关键字与super关键字
  12. 大白菜PE系统查看电脑开机密码教程
  13. 一年级古诗《画》知识点心田花开汇总
  14. 【项目技术点总结之二】微信小程序中集成antvF2进行图表开发
  15. 基于babylon3D模型研究3D骨骼动画(1)
  16. 华为路由器DNS服务器未响应,路由器dns辅服务器未响应
  17. 纯净简洁绿色的解压缩软件
  18. R语言 绘图 (ggplot2)
  19. 抖音企业号获客系统技术操作手册
  20. VS2013编译libjpeg库

热门文章

  1. QT的QDrag类的使用
  2. C语言合并排序实例代码
  3. java下載與安裝_[Java] 下載與安裝Java官方開發工具:NetBeans IDE
  4. linux手动注入网络数据_Linux网络 - 数据包的接收过程
  5. SpringBoot与quartz框架实现分布式定时任务
  6. node软件环境安装
  7. Js/Jquery获取iframe中的元素 在Iframe中获取父窗体的元素方法
  8. 怎样在Ubuntu 14.04中搭建gitolite git服务器
  9. JavaGUI中的JComboBox的处理
  10. Python dataframe列拆分多行与统计