• 使用 Mockito 辅助单元测试


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

  • 相关阅读:
    二、有限状态机(FSM)
    一、同步状态机
    quartus ii 中文注释乱码解决办法
    基于FPGA的线阵CCD图像测量系统研究——笔记
    数据接口的同步方法
    Servlet和web服务器关系
    实现项目本地,测试,生产3套环境
    Tomcat--startup.bat文件
    Servlet--HttpUtils类
    Servlet--HttpSessionBindingListener接口,HttpSessionBindingEvent类
  • 原文地址:https://www.cnblogs.com/Downstream-1998/p/11632264.html
Copyright © 2020-2023  润新知