了解过单元测试相关概念的人应该会清楚一个概念:一个好的单元测试应该是与环境无关的,每个测试都是相互独立的。亦即你能够在任何地方,以任意顺序运行这些测试,最后获得的结果是同样的。可是我被测试的类/方法中自己夹杂着对其它类的依赖,这又该怎么处理呢,将依赖进行 mock 是其中一个作法。本文将记录我在测试过程当中的一些备忘,以及遇到的一些问题。java
背景说明
我要对我正在开发的一个考试系统中的题目管理部分进行单元测试,这部分主要有一个 SubjectService
接口及其对应的实现类 SubjectServiceImpl
,Service 内部又依赖于 DAO 层的两个 Mapper(SubjectMapper
和 SubjectAnswerMapper
)。如今我要对 Service 层进行单元测试。此为背景。git
过程
首先要肯定一个概念:测 Service 层,咱们要测它的什么?Service 层对数据库的访问是经过 DAO 层进行的。那么对数据库相关的操做就不适宜放在这里进行测试(对它们的测试应该放在 DAO 层)。Service 层做为主要业务逻辑的载体,对 Service 层的测试应该围绕流程进行(对于不合法的输入,应该抛出对应的异常;对于正常的输入,则流程应该能正常走完,至于数据库访问的正确与否,交给 DAO 层的单元测试进行保证)。github
肯定了这一点以后,接下来就能够开始测试流程了。首先是引入相关的测试框架。因为项目采用了 SpringBoot,我参考了参考资料中的内容,构建起整个测试环境的依赖。spring
而后就是开始编写相关的测试类:数据库
@RunWith(MockitoJUnitRunner.class) public class SubjectServiceImplTest { private SubjectServiceImpl subjectServiceImpl; }
对于 Service 所依赖的两个 DAO,只须要建立对应的两个 Mapper 并为其加上 @Mock
注解,而后在被测试对象上加上 @InjectMocks
注解,即完成了对依赖的 mock:springboot
@RunWith(MockitoJUnitRunner.class) public class SubjectServiceImplTest { @Mock private SubjectMapper subjectMapper; @Mock private SubjectAnswerMapper subjectAnswerMapper; @InjectMocks private SubjectServiceImpl subjectServiceImpl; }
而后就能够开始测试咱们的 Service 的方法了。因为 Mock 的引入,如今测试方法的整个流程变成了 4 个步骤:app
- 准备测试用的输入
- 给 Mock 对象设置预期的输出(由于被测对象所依赖的是由你虚拟出来的东西,因此依赖应该怎么响应须要你手动设置)
- 运行被测方法
- 检查运行结果是否与预期一致
如下是一个例子框架
/** * 测试插入没有答案的试题 * 应该抛出异常 */ @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 的部分没法进行测试,后续我会继续查阅相关资料并更新此文。