单元测试系列之5:使用unitils测试Service层
程序员文章站
2022-04-16 12:05:31
...
引述:Spring 的测试框架为我们提供一个强大的测试环境,解决日常单元测试中遇到的大部分测试难题:如运行多个测试用例和测试方法时,Spring上下文只需创建一次;数据库现场不受破坏;方便手工指定Spring配置文件、手工设定Spring容器是否需要重新加载等。但也存在不足的地方,基本上所有的Java应用都涉及数据库,带数据库应用系统的测试难点在于数据库测试数据的准备、维护、验证及清理。Spring 测试框架并不能很好地解决所有问题。要解决这些问题,必须整合多方资源,如DbUnit、Unitils、Mokito等。其中Unitils正是这样的一个测试框架。
使用unitils测试Service层
在进行服务层的测试之前,我们先来认识一下需要测试的UserServiceImpl服务类。UserServiceImpl服务类中拥有一个处理用户登录的服务方法,其代码如下所示。
UserService.java
UserServiceImpl需要调用DAO层的UserDao和LoginLogDao以及User和LoginLog这两个PO完成业务逻辑,User和LoginLog分别对应t_user和t_login_log这两张数据库表。
在用户登录成功后调用UserServiceImpl中的loginSuccess()方法执行用户登录成功后的业务逻辑。
这是一个需要访问数据库并存在数据更改操作的业务方法,它工作在事务环境下。下面是装配该服务类Bean的Spring配置文件。
baobaotao-service.xml
UserServiceImpl所关联的DAO类和PO类都比较简单,这里就不一一列出,读者可以参考本文附带光盘中的实例代码。在着手测试UserServiceImpl之前,需要先创建数据库表,相应的SQL脚本文件位于:D:\masterSpring\Chapter 16\schema目录下。
下面我们为UserServiceImpl编写一个简单的测试用例类,此时的目标是让这个基于Unitils测试框架的测试类运行起来,并联合Mockito框架创建Dao模拟对象。首先编写测试UserService#findUserByUserName()方法的测试用例,如代码清单16-37所示:
UserServiceTest.java
这里,我们让UserServiceTest直接继承于Unitils所提供的UnitilsJUnit4的抽象测试类,该抽象测试类的作用是让Unitils测试框架可以在JUnit 测试框架基础上运行起来。在①处,标注了一个类级的@SpringApplicationContext注解,这里Unitils将从类路径中加载Spring配置文件,并使用该配置文件启动Spring容器。在③处通过Mockito创建两个模拟DAO实例。在④-1处模拟测试数据并通过Mockito录制UserDao#findUserByUserName()行为。在④-2处实例化用户服务实例类,并在④-3处通过Spring测试框架提供的工具类org.springframework.test.util.ReflectionTestUtils为userService私有属性userDao赋值(ReflectionTestUtils是一个访问测试对象中私有属性非常好用的工具类)。在④-4处调用服务UserService#findUserByUserName()方法,并验证返回结果。在④-5处通过Mockito验证模拟userDao对象是否被调用,且只调用一次。最后在IDE中执行UserServiceTest测试用例,测试结果如图16-15所示。
从运行结果可以看出,我们已成功对UserServceTest.findUserByUserName()执行单元测试。下面我们通过Unitils提供的@DataSet注解来准备测试数据,并测试UserService# loginSuccess ()方法。BaobaoTao.SaveUsers.xls数据集如图16-16所示。
准备好了测试数据集之后,就可以开始为UserServiceImpl编写测试用例类,此时的目标是通过Unitils提供的@DataSet注解准备测试数据,来保证测试数据的独立性,避免手工通过事务回滚维护测试数据的状态。测试UserService#loginSuccess ()方法的代码如下所示。
代码清单16 38 UserServiceTest.java
在①处通过加载Unitils的@SpringApplicationContext 注解加载Spring配置文件,并初始化Spring容器。在②处通过@ SpringBean注解从Spring容器中获取UserService实例。在③处通过@DataSet注解从当前测试用例所在类路径中加载BaobaoTao.SaveUsers.xls数据集,并将数据集中的数据保存到测试数据库相应的表中。从上面的数据集中可以看出,我们为t_user表准备了两条用户信息测试数据。在④-1处从测试数据库中获取“tom”用户信息,模拟当前登录的用户。在④-2处设置当前“tom”用户的登录时间。在④-3处调用UserService#loginSuccess()方法,更新“tom”用户积分,并持久化到测试数据库中。在⑤处,验证“tom”用户当前积分是否是105分。完成测试用例的编写,最后在IDE中执行UserServiceTest测试用例,测试结果如图16-17所示。
从运行结果可以看出,我们已成功对UserServce#loginSuccess()执行单元测试。重复执行当前单元测试,测试结果仍然通过。细心的读者可能会有疑问,没有UserServce# loginSuccess()测试方法实施事务回滚,执行多次之后“tom”用户的积分不应该是105分,那为何测试还是通过呢?这是因为Unitils帮我们维护测试数据库中的数据状态,Unitils这个强大的魔力,归根于Unitils强大的数据集更新策略。到此我们成功完成UserServce单元测试。从上面为用户服务UserServce编写两个测试方法可以看出,对service层的测试,我们既可以采用JUnit+Unitils+Mockito组合,运用Mockito强大的模块能力,完成service层独立性测试,也可以采用JUnit+Unitils+Dbunit组合,运用Dbunit强大的数据库维护能力,完成service层+DAO层集成测试。
这些文章摘自于我的《Spring 4.x企业应用开发实战》的第16章,我将通过连载的方式,陆续在此发出。欢迎大家讨论。
使用unitils测试Service层
在进行服务层的测试之前,我们先来认识一下需要测试的UserServiceImpl服务类。UserServiceImpl服务类中拥有一个处理用户登录的服务方法,其代码如下所示。
UserService.java
package com.baobaotao.service; import com.baobaotao.domain.LoginLog; import com.baobaotao.domain.User; import com.baobaotao.dao.UserDao; import com.baobaotao.dao.LoginLogDao; @Service("userService") public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Autowired private LoginLogDao loginLogDao; public void loginSuccess(User user) { user.setCredits( 5 + user.getCredits()); LoginLog loginLog = new LoginLog(); loginLog.setUserId(user.getUserId()); loginLog.setIp(user.getLastIp()); loginLog.setLoginTime(user.getLastVisit()); userDao.updateLoginInfo(user); loginLogDao.insertLoginLog(loginLog); } … }
UserServiceImpl需要调用DAO层的UserDao和LoginLogDao以及User和LoginLog这两个PO完成业务逻辑,User和LoginLog分别对应t_user和t_login_log这两张数据库表。
在用户登录成功后调用UserServiceImpl中的loginSuccess()方法执行用户登录成功后的业务逻辑。
[1] 登录用户添加5个积分(t_user.credits)。
[2] 将登录用户的最后访问时间(t_user.last_visit)和IP(t_user.last_ip)更新为当前值。
[3] 在日志表(t_login_log)中为用户添加一条登录日志。
这是一个需要访问数据库并存在数据更改操作的业务方法,它工作在事务环境下。下面是装配该服务类Bean的Spring配置文件。
baobaotao-service.xml
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <context:component-scan base-package="com.baobaotao.service"/> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource" /> <tx:annotation-driven /> <!-- 使用aop/tx命名空间配置事务管理,这里对service包下的服务类方法提供事务--> <aop:config> <aop:pointcut id="jdbcServiceMethod" expression= "within(com.baobaotao.service..*)" /> <aop:advisor pointcut-ref="jdbcServiceMethod" advice-ref="jdbcTxAdvice" /> </aop:config> <tx:advice id="jdbcTxAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice> </beans>
UserServiceImpl所关联的DAO类和PO类都比较简单,这里就不一一列出,读者可以参考本文附带光盘中的实例代码。在着手测试UserServiceImpl之前,需要先创建数据库表,相应的SQL脚本文件位于:D:\masterSpring\Chapter 16\schema目录下。
下面我们为UserServiceImpl编写一个简单的测试用例类,此时的目标是让这个基于Unitils测试框架的测试类运行起来,并联合Mockito框架创建Dao模拟对象。首先编写测试UserService#findUserByUserName()方法的测试用例,如代码清单16-37所示:
UserServiceTest.java
package com.baobaotao.service; import org.unitils.UnitilsJUnit4; import org.unitils.spring.annotation.SpringApplicationContext; import org.springframework.test.util.ReflectionTestUtils; import org.unitils.spring.annotation.SpringBean; import org.junit.Test; import com.baobaotao.domain.User; import java.util.Date; … @SpringApplicationContext({"baobaotao-service.xml", "baobaotao-dao.xml"}) //①加载Spring配置文件 public class UserServiceTest extends UnitilsJUnit4{ private UserDao userDao; //② 声明用户Dao private LoginLogDao loginLogDao; @Before //③ 创建Dao模拟对象 public void init(){ userDao = mock(UserDao.class); loginLogDao = mock(LoginLogDao.class); } @Test //④ 设置成为JUnit测试方法 public void findUserByUserName() { //④-1 模拟测试数据 User user = new User(); user.setUserName("tom"); user.setPassword("1234"); user.setCredits(100); doReturn(user).when(userDao).findUserByUserName("tom"); //④-2 实例化用户服务实例类 UserServiceImpl userService = new UserServiceImpl(); //④-3通过Spring测试框架提供的工具类为目标对象私有属性设值 ReflectionTestUtils.setField(userService, "userDao", userDao); //④-4 验证服务方法 User u = userService.findUserByUserName("tom"); assertNotNull(u); assertThat(u.getUserName(),equalTo(user.getUserName())); //④-5 验证交互行为 verify(userDao,times(1)).findUserByUserName("tom"); } }
这里,我们让UserServiceTest直接继承于Unitils所提供的UnitilsJUnit4的抽象测试类,该抽象测试类的作用是让Unitils测试框架可以在JUnit 测试框架基础上运行起来。在①处,标注了一个类级的@SpringApplicationContext注解,这里Unitils将从类路径中加载Spring配置文件,并使用该配置文件启动Spring容器。在③处通过Mockito创建两个模拟DAO实例。在④-1处模拟测试数据并通过Mockito录制UserDao#findUserByUserName()行为。在④-2处实例化用户服务实例类,并在④-3处通过Spring测试框架提供的工具类org.springframework.test.util.ReflectionTestUtils为userService私有属性userDao赋值(ReflectionTestUtils是一个访问测试对象中私有属性非常好用的工具类)。在④-4处调用服务UserService#findUserByUserName()方法,并验证返回结果。在④-5处通过Mockito验证模拟userDao对象是否被调用,且只调用一次。最后在IDE中执行UserServiceTest测试用例,测试结果如图16-15所示。
从运行结果可以看出,我们已成功对UserServceTest.findUserByUserName()执行单元测试。下面我们通过Unitils提供的@DataSet注解来准备测试数据,并测试UserService# loginSuccess ()方法。BaobaoTao.SaveUsers.xls数据集如图16-16所示。
准备好了测试数据集之后,就可以开始为UserServiceImpl编写测试用例类,此时的目标是通过Unitils提供的@DataSet注解准备测试数据,来保证测试数据的独立性,避免手工通过事务回滚维护测试数据的状态。测试UserService#loginSuccess ()方法的代码如下所示。
代码清单16 38 UserServiceTest.java
package com.baobaotao.service; import org.unitils.UnitilsJUnit4; import org.unitils.spring.annotation.SpringApplicationContext; import org.unitils.spring.annotation.SpringBean; import org.junit.Test; import com.baobaotao.domain.User; import java.util.Date; … @SpringApplicationContext({"baobaotao-service.xml", "baobaotao-dao.xml"}) //①加载Spring配置文件 public class UserServiceTest extends UnitilsJUnit4{ //② 从Spring容器中加载UserService实例 @SpringBean("userService") private UserService userService; @Test @DataSet("BaobaoTao.SaveUsers.xls")//③ 准备验证数据 public void loginSuccess() { User user = userService.findUserByUserName("tom"); //④-1 加载"tom"用户信息 Date now = new Date(); user.setLastVisit(now); //④-2 设置当前登录时间 userService.loginSuccess(user); //④-3 user登录成功,更新其积分及添加日志 User u = userService.findUserByUserName("tom"); assertThat(u.getCredits(),is(105)); //⑤ 验证登录成功之后,用户积分 } }
在①处通过加载Unitils的@SpringApplicationContext 注解加载Spring配置文件,并初始化Spring容器。在②处通过@ SpringBean注解从Spring容器中获取UserService实例。在③处通过@DataSet注解从当前测试用例所在类路径中加载BaobaoTao.SaveUsers.xls数据集,并将数据集中的数据保存到测试数据库相应的表中。从上面的数据集中可以看出,我们为t_user表准备了两条用户信息测试数据。在④-1处从测试数据库中获取“tom”用户信息,模拟当前登录的用户。在④-2处设置当前“tom”用户的登录时间。在④-3处调用UserService#loginSuccess()方法,更新“tom”用户积分,并持久化到测试数据库中。在⑤处,验证“tom”用户当前积分是否是105分。完成测试用例的编写,最后在IDE中执行UserServiceTest测试用例,测试结果如图16-17所示。
从运行结果可以看出,我们已成功对UserServce#loginSuccess()执行单元测试。重复执行当前单元测试,测试结果仍然通过。细心的读者可能会有疑问,没有UserServce# loginSuccess()测试方法实施事务回滚,执行多次之后“tom”用户的积分不应该是105分,那为何测试还是通过呢?这是因为Unitils帮我们维护测试数据库中的数据状态,Unitils这个强大的魔力,归根于Unitils强大的数据集更新策略。到此我们成功完成UserServce单元测试。从上面为用户服务UserServce编写两个测试方法可以看出,对service层的测试,我们既可以采用JUnit+Unitils+Mockito组合,运用Mockito强大的模块能力,完成service层独立性测试,也可以采用JUnit+Unitils+Dbunit组合,运用Dbunit强大的数据库维护能力,完成service层+DAO层集成测试。
引用
提示:在实际项目中,我们只对DAO做集成测试,在对Service层测试中,对所有的DAO都用Mockito模拟,也即只对Service层单元测试。
这些文章摘自于我的《Spring 4.x企业应用开发实战》的第16章,我将通过连载的方式,陆续在此发出。欢迎大家讨论。