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

    应客户需求,要求实现一个版本一键升级的功能,咨询过同事之后弄了个demo出来,后台代码如下: //DBConnModelInfo:连接字符串的对象 (包含数据库实例名,数据库名,登陆名,登陆密码) p ...

  2. Markdown温故知新(1):Markdown面面观

    1.什么是 Markdown? 2.有哪些人在用 Markdown? 3.用 Markdown 的优势是什么? 4.Markdown 的语法标准简介 5.怎么用 Markdown? 6.如何选择 Ma ...

  3. C# 拓展ComboBox设置线条属性(转)

    C# 拓展ComboBox设置线条属性目前由于项目需要,要实现线条属性设置的功能,就如Visio中点击线端时,可以弹出一个窗口设置线条的各种属性. 其中线条属性选择时,是在ComboBox控件中,显示 ...

  4. android studio学习----The project encoding (windows-1252) does not match the encoding specified in the Gradle build files (UTF-8)

    Warning:The project encoding (windows-1252) does not match the encoding specified in the Gradle buil ...

  5. Flink流式计算

    Structured Streaming A stream is converted into a dynamic table. A continuous query is evaluated on ...

  6. windows系统DOC命令启动或停止服务

    -- 启动服务 -- net start Mysql -- 停止服务 -- net stop Mysql-- 说明:MysqL没有大小写区分.可以直接小写mysql. -- 启动报错(net star ...

  7. Linux文件服务管理之vsftpd

    简介 vsftpd是 "very secure FTP deamon"的缩写,是一个完全免费,开源的ftp服务器软件. 特点 小巧轻快,安全易用,支持虚拟用户.支持带宽限制等功能. ...

  8. Linux使用pt-archiver工具自动备份MySQL

    操作系统: CentOS 6.9 脚本语言: shell https://github.com/iscongyang/Practical/blob/master/shell-scripts/pt-ar ...

  9. Go如何使用数据库、缓存

    database/sql 接口 Go官方没有提供数据库驱动,而是为开发数据库驱动定义了一些标准接口database/sql,开发者可以根据定义的接口来开发相应的数据库驱动,这样做有一个好处,只要是按照 ...

  10. 在istio中让prometheus跑起来

    使用microk8s安装,默认的prometheus已就位. 可直接弄. 一,映射本地端口(注意,命令行最后的两个端口,前一个为要映射的本地端口,后一个为POD的服务端口,如果本地相同端口被占用,则要 ...