了解过单元测试相关概念的人应该会清楚一个概念:一个好的单元测试应该是与环境无关的,每一个测试都是相互独立的。亦即你可以在任何地方,以任意顺序运行这些测试,最后得到的结果是一样的。但是我被测试的类/方法中本身夹杂着对其它类的依赖,这又该怎么处理呢,将依赖进行 mock 是其中一个做法。本文将记录我在测试过程中的一些备忘,以及遇到的一些问题。

背景说明

我要对我正在开发的一个考试系统中的题目管理部分进行单元测试,这部分主要有一个 SubjectService 接口及其对应的实现类 SubjectServiceImpl,Service 内部又依赖于 DAO 层的两个 Mapper(SubjectMapperSubjectAnswerMapper)。现在我要对 Service 层进行单元测试。此为背景。

过程

首先要确定一个概念:测 Service 层,我们要测它的什么?Service 层对数据库的访问是通过 DAO 层进行的。那么对数据库相关的操作就不适宜放在这里进行测试(对它们的测试应该放在 DAO 层)。Service 层作为主要业务逻辑的载体,对 Service 层的测试应该围绕流程进行(对于不合法的输入,应该抛出对应的异常;对于正常的输入,则流程应该能正常走完,至于数据库访问的正确与否,交给 DAO 层的单元测试进行保证)。

确定了这一点之后,接下来就可以开始测试流程了。首先是引入相关的测试框架。由于项目采用了 SpringBoot,我参考了参考资料中的内容,构建起整个测试环境的依赖。

然后就是开始编写相关的测试类:

@RunWith(MockitoJUnitRunner.class)
public class SubjectServiceImplTest {
private SubjectServiceImpl subjectServiceImpl;
}

对于 Service 所依赖的两个 DAO,只需要创建对应的两个 Mapper 并为其加上 @Mock 注解,然后在被测试对象上加上 @InjectMocks 注解,即完成了对依赖的 mock:

@RunWith(MockitoJUnitRunner.class)
public class SubjectServiceImplTest {
@Mock
private SubjectMapper subjectMapper; @Mock
private SubjectAnswerMapper subjectAnswerMapper; @InjectMocks
private SubjectServiceImpl subjectServiceImpl;
}

然后就可以开始测试我们的 Service 的方法了。由于 Mock 的引入,现在测试方法的整个流程变成了 4 个步骤:

  1. 准备测试用的输入
  2. 给 Mock 对象设置预期的输出(因为被测对象所依赖的是由你虚拟出来的东西,所以依赖应该怎么响应需要你手动设置)
  3. 运行被测方法
  4. 检查运行结果是否与预期一致

以下是一个例子

/**
* 测试插入没有答案的试题
* 应该抛出异常
*/
@Test
public void testSaveSubjectWithoutAnswer() {
SubjectDTO subjectDTO = new SubjectDTO();
subjectDTO.setName("testSubject");
subjectDTO.setDifficulty(1L);
subjectDTO.setCategoryId(1L);
subjectDTO.setSubjectTypeId(1L); Mockito.when(subjectMapper.insert(Mockito.any()))
.thenReturn(1); try {
subjectService.saveSubject(subjectDTO);
} catch (BusinessException e) {
assertEquals(e.getCode(), ResultEnum.INCOMPLETE_ADD_EXERCISE_INFORMATION.getCode());
return;
}
throw new RuntimeException("Should not reach here!");
}

在上面的例子中,步骤 2 使用到了 Mockito 类的一些静态方法,设定了 Mapper 里会被调用方法的响应。(这里建议为了简化代码,可以通过 import static 的方式引入 Mockito 的所有方法,这样可以省略前面的类名)受限于我使用的 JUnit 为 JUnit 4,所以对异常的测试只能这样进行,在 JUnit 5 中就添加了对预期抛出异常的 assert。

会做这个测试,其余的测试也就基本能够进行下去了。

遇到的问题

在跑的过程中,我发现了一个挺棘手的问题,目前还没找到合适的方案。

项目的 DAO 层使用的是 MyBatis + 通用 Mapper 这一套框架。我在 Mock 方法的时候发现在运行的过程中,有关 Mapper 方法中的 selectByExample 的部分总是运行不了,我在方法内部写了创建 Example 的过程,如果使用 Mock,创建 Example 的过程会出现异常,内容大概是要依赖一个数据库环境。所以在不考虑 Service 和 DAO 集成测试的情况下,涉及到这部分的 Service 的部分无法进行测试,后续我会继续查阅相关资料并更新此文。

参考资料

SpringBoot2.x 单元测试 | 闪烁之狐

使用 Mockito 辅助单元测试的更多相关文章

  1. 基于Springboot+Junit+Mockito做单元测试

    前言 前面的两篇文章讨论过< 为什么要写单元测试,何时写,写多细 >和<单元测试规范>,这篇文章介绍如何使用Springboot+Junit+Mockito做单元测试,案例选取 ...

  2. 使用Mockito进行单元测试【2】—— stub 和 高级特性[转]

    一篇中介绍了Mockito的基本信息,现在接着介绍Mockito强大的stub功能 2. Mockito使用实例 5. 对连续的调用进行不同的返回 (iterator-style stubbing) ...

  3. 使用Mockito进行单元测试【1】——mock and verify[转]

    本文转自:http://qiuguo0205.iteye.com/blog/1443344 1. 为什么使用Mockito来进行单元测试? 回答这个问题需要回答两个方面,第一个是为什么使用mock?m ...

  4. 使用 Junit + Mockito 实践单元测试

    一.前言 相信做过开发的同学,都多多少少写过下面的代码,很长一段时间我一直以为这就是单元测试... @SpringBootTest @RunWith(SpringRunner.class) publi ...

  5. Mockito+Junit5单元测试

    参考: https://segmentfault.com/a/1190000006746409 https://waylau.com/mockito-quick-start/ 1.引入依赖 下面这个最 ...

  6. powermock+mockito+testng 单元测试pom文件

    0:Supported versions PowerMock version 1.7.0 and upper has experimental support of Mockito 2. A lot ...

  7. 单元测试系列:Mock工具之Mockito实战

    更多原创测试技术文章同步更新到微信公众号 :三国测,敬请扫码关注个人的微信号,感谢! 原文链接:http://www.cnblogs.com/zishi/p/6780719.html 在实际项目中写单 ...

  8. 单元测试系列之五:Mock工具之Mockito实战

    更多原创测试技术文章同步更新到微信公众号 :三国测,敬请扫码关注个人的微信号,感谢! 原文链接:http://www.cnblogs.com/zishi/p/6780719.html 在实际项目中写单 ...

  9. 单元测试---googletest

    单元测试概述 测试并不只是测试工程师的责任,对于开发工程师,为了保证发布给测试环节的代码具有足够好的质量( Quality ),为所编写的功能代码编写适量的单元测试是十分必要的. 单元测试( Unit ...

随机推荐

  1. 洛谷 p1387最大正方形

    洛谷 p1387最大正方形 题目描述 在一个n*m的只包含0和1的矩阵里找出一个不包含0的最大正方形,输出边长. 输入格式 输入文件第一行为两个整数n,m(1<=n,m<=100),接下来 ...

  2. DjangoDRF之视图总结

    思维导图xmind文件:https://files-cdn.cnblogs.com/files/benjieming/DRF%E6%A8%A1%E5%9D%97%E4%B9%8B%E8%A7%86%E ...

  3. Lucene PriorityQueue & JDK PriorityQueue

    麻蛋,原来是最小堆呀!  数据结构不熟害死人呀! 看来待复习复习数据结构了 在lucene源码中对多个段合并的时候,会先将多个段放到一个PriorityQueue中,不要被这个名字迷惑,这个Prior ...

  4. 349套HTML5+CSS3各行各业网站模板免费下载

    场景 349套HTML5+CSS3各行各业网站模板. Web前端开发是从网页制作演变而来的,名称上有很明显的时代特征.在互联网的演化进程中,网页制作是Web1.0时代产物,那时网站的主要内容是静态的, ...

  5. SpringBoot自定义Condition注解

        最近碰到个这样的需求,需要同一套代码适配个版本数据库(数据库不同,且部分表的字段及关联关系可能会不同),即这套代码配置不同的数据库都能跑.项目采用的框架为SpringBoot+Mybatis. ...

  6. SPring boot jpa 封装查询条件

    最近使用spring data jpa做了两个项目,对于动态查询的不友好做了个类似hibernate的封装,记录也分享下 首先定义一个所有条件的容器,继承Specification /** * 定义一 ...

  7. 【LINQ】Select与SelectMany的区别

    Select() 和 SelectMany() 的工作都是依据源值生成一个或多个结果值.Select() 为每个源值生成一个结果值.因此,总体结果是一个与源集合具有相同元素数目的集合.与之相反,Sel ...

  8. 【学习笔记】大数据技术原理与应用(MOOC视频、厦门大学林子雨)

    1 大数据概述 大数据特性:4v volume velocity variety value 即大量化.快速化.多样化.价值密度低 数据量大:大数据摩尔定律 快速化:从数据的生成到消耗,时间窗口小,可 ...

  9. Ubuntu Pycharm下deeplab ImportError: cannot import name 'monitoring'

    1.使用pycharm运行deeplab过程中出现ImportError: cannot import name 'monitoring' 2.把root用户及非root用户中pip安装的tensor ...

  10. JavaScript中数组去重汇总

    1. 简单的去重方法,利用数组的indexOf下标属性来查询 /* * 新建一新数组,遍历传入数组,值不在新数组就push进该新数组中 * IE8以下不支持数组的indexOf方法 * */ func ...