了解过单元测试相关概念的人应该会清楚一个概念:一个好的单元测试应该是与环境无关的,每一个测试都是相互独立的。亦即你可以在任何地方,以任意顺序运行这些测试,最后得到的结果是一样的。但是我被测试的类/方法中本身夹杂着对其它类的依赖,这又该怎么处理呢,将依赖进行 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. Sql与Oracle的差异

    /*整理背景201403订单中心数据库迁移(整理Oracle与SQL的差异)整理规则第一句为SQL Server 第二句为Oracle*/--数据类型int integervarchar varcha ...

  2. Vue项目运行或打包时,频繁内存溢出情况CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

    前端使用基于vue的Nuxt框架,但是随着项目功能增多,项目变大,频繁出现此种情况,原因是项目太大,导致内存溢出,排除代码问题外,可参照以下方式解决 解决方案 1.全局安装increase-memor ...

  3. nginx 反向代理时丢失端口的解决方案(转)

    今天,配置nginx反向代理时遇到一个问题,当设置nginx监听80端口时转发请求没有问题.但一旦设置为监听其他端口,就一直跳转不正常:如,访问欢迎页面时应该是重定向到登录页面,在这个重定向的过程中端 ...

  4. oracle OCCI编程

    1. 创建OCCI环境变量 Environment *env = Environment::createEnvironment(); Environment对象的建立必须放在第一位,而且也必须是最后一 ...

  5. MySQL容器化详细教程

    前言:  上篇文章介绍了Docker工具的安装及常用命令使用.本篇文章我们会介绍如何在Docker中运行MySQL实例,可能有的小伙伴会问:为什么要在Docker里运行MySQL呢?因为在Docker ...

  6. 深入理解Java封装、继承、多态

    转载请注明原文地址:https://www.cnblogs.com/ygj0930/p/10830957.html 一:封装 将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法 ...

  7. sql字符转义

    /** * sql 内的like % 代表一个或多个字符, _代表一个字符,这都是需要转义的 * @param s * @return */ public static String escapeSp ...

  8. 团队第五次——Alpha2的发布

    这个作业属于哪个课程 https://edu.cnblogs.com/campus/xnsy/2019autumnsystemanalysisanddesign/ 这个作业要求在哪里 https:// ...

  9. antd快速开发(Form篇)

    antd快速开发(Form篇) 前言 由于一直在做中台业务,后台项目特别多,但是后台项目的特点是:大量的列表和大量表单,重复开发会降低效率,所以我这边总结了一下使用antd组件搭建form的快捷方法. ...

  10. LOJ 3120: 洛谷 P5401: 「CTS2019 | CTSC2019」珍珠

    题目传送门:LOJ #3120. 题意简述: 称一个长度为 \(n\),元素取值为 \([1,D]\) 的整数序列是合法的,当且仅当其中能够选出至少 \(m\) 对相同元素(不能重复选出元素). 问合 ...