摘要:在系统开发的过程中,单元测试是其中的一个重要环节。在Java微服务项目中,Spring框架本身就为我们提供了一套单元测试的框架SpringBootTest。如果我们在学校完成课堂作业或出于兴趣爱好自学,是可以使用Spring自带的单元测试框架进行单测的。
工作中,这种通过SpringBootTest进行单元测试的方式则不推荐使用。其缺点在于,每次执行测试方法都必须启动Spring容器。当项目规模较大、配置较为复杂时,即使只对一个方法进行测试,也需要消耗大量时间启动Spring容器。当我们期望对DAO层方法进行测试时,该方法还有其他缺点:① 如果忘记加进行事务控制的注解,将可能导致数据库产生“脏数据”或数据缺失。② 当查询语句涉及大量连表查询时,查询效率可能十分低下,执行速度缓慢。③ 由于必须根据数据库中已有的数据来编写测试条件,每次必须先去数据库确保哪些数据存在、哪些数据不存在,再编写对应返回正确值、返回错误值的单元测试,开发效率低下。
针对上述问题,可能有人会想到使用H2内存数据库的方式加以解决。不过,这依然无法有效地解决执行单元测试需要启动Spring容器的问题和上述问题③,假设我们期望执行用户查询返回一条姓名为xxx、年龄为xxx的记录,我们依然需要去sql文件中编写这一条记录的插入语句,并且也需要大量的配置。如果有很多条需要模拟的数据记录,就需要创建很多表、编写很多sql语句,开发效率依然大打折扣。
此时,有一种很好的解决方案,既不需要和真实的数据库交互,也不需要启动Spring容器,同时又不需要编写大量的测试数据源,它就是Mock。使用Mock进行单元测试,我们可以直接模拟出结果,而不需要准备数据源。本文以简单的用户功能为例,说明如何使用Mock来进行DAO层的单元测试。
1、使用Spring原生的方法进行测试。我们假设ID=1的用户记录是存在的,那么查询结果必定不为NULL。假设ID=2的用户记录是不存在的,那么查询结果必定为NULL。该方式需要启动Spring容器,并与数据库发生真实交互。
2、使用Mock进行测试。该方式不需要启动Spring容器,也不与数据库发生真实交互。
2.1、首先,引入Mock所需的pom依赖
2.2、使用运行Mock框架的注解@RunWith(MockitoJUnitRunner.class)
替换Spring原生单元测试的注解@SpringBootTest
2.3、给Service层对象加上@InjectMocks注解,给Dao层对象加上@Mock注解。其中,@InjectMocks注解对象的方法会进行真实调用(会真实调用已编写的代码并返回执行结果),而@Mock注解对象的方法则是进行模拟调用(不会真实调用已编写的代码并返回我们设置的预期执行结果)。
2.4、具体的单元测试方法中,通过Mockito.when(模拟方法).thenReturn(预期返回值)的方式,进行单元测试。
上述方法中,“Mockito.when(userDAO.findUserById(1L)).thenReturn(new User())”的含义是,当userDAO调用findUserById进行查询且参数为1L时,会返回一个new的User对象。
同理,“Mockito.when(userDAO.findUserById(2L)).thenReturn(null)”的含义是,当userDAO调用findUserById进行查询且参数为2L时,会返回一个空对象。
当测试涉及的数据记录较多,逻辑较复杂时,使用Mock模拟DAO层的测试所提升单元测试的执行效率将更加明显。
此外,当我们本地在开发调试时,如果数据库的测试数据发生了改变,那么我们单元测试的结果也会受到影响。例如,数据库中原本存在ID=1的记录,如果不小心删掉了,那么我们单测中Assert.assertNotNull的方法就会报错。而如果使用Mock的形式,无论数据库中是否存在该记录,我们执行DAO层方法的返回值都只依赖于我们在thenReturn方法中设置的值。
总结一下使用Mock模拟DAO层方法测试的优点:
1、不需要启动Spring容器
2、不需要与数据库发生真实交互,不会导致脏数据产生、不会受到数据库真实数据的影响、不需要为了单元测试额外添加/修改/删除数据
3、启动速度快、执行速度快、开发简单且效率高
作者:孙俊辉