一、unitils测试框架优缺点介绍
在unitils的支持下,xml配置的spring项目在测试时,有如下好处:
1、利用注解@DataSet、@ExpectedDataSet来准备数据和校验结果数据,每次运行测试用例不用重新准备数据。
2、利用@Transactional来配置测试时的事务模式:COMMIT(测试后提交数据)、ROLLBACK(测试后回滚数据,不会污染测试数据库)
3、可以基于单独的单元测试数据库进行单元测试
4、等等
利用unitils来进行单元测试,好处多多,单元测试对于开发人员来说,至关重要,首先可以检查程序是否按照预期运行,其次,过段时间后,只需要运行一下单元测试,就可以检车程序是否被修改出错,及时检查出问题。
但是,上述基于unitils的spring项目的单元测试都是在xml配置文件的基础之上,目前很多项目都是spring-boot项目,最新的unitils-3.4.6也不支持spring-boot的项目,因此,这里在查看unitils的源码的基础上,做了一个小的开源项目:spring-boot-unitils-starter来给spring-boot项目提供基于unitils支持。
二、spring-boot-unitils-starter项目介绍
1、项目结构如下
2、问题分析
在一中介绍到了unitils只支持xml形式的spring 项目,spring-boot项目是去配置化了,而且查看unitils的源码得知,测试中用到的bean都是从一个applicationContext中获取之后再设置到对应的属性(通过注解@SpringBeanByType、@SpringBeanByName来实现)中,因此spring-boot-unitils-starter项目的重点就是将spring-boot应用的applicationContext获取到然后设置到unitils中的applicationContext。
3、主要功能点
3.1、spring-boot应用applicationContext的获取与设置
@Configuration
@ConditionalOnClass(UnitilsBootBlockJUnit4ClassRunner.class)
public class ConfigurableApplicationContextAware implements InitializingBean {
@Autowired
private ConfigurableApplicationContext configurableApplicationContext;
@Override
public void afterPropertiesSet() throws Exception {
SpringBootModule.setApplicationContext(configurableApplicationContext);
}
}
这里的@ConditionalOnClass就是为了在测试的时候才声明这个bean(ConfigurableApplicationContextAware),UnitilsBootBlockJUnit4ClassRunner是单元测试的ClassRunner。
3.2、spring项目运行unitils的核心类SpringBootModule
原始的unitils提供的是SpringModule,applicationContext就是该类的一个属性,并且只能get,不能set,为了和原始的SpringModule区分开,并且考虑到我们测试的应用是spring-boot,因此这里就新建了SpringBootModule,修改applicationContext为static,并且给applicationContext设置了静态set方法。
private static ApplicationContext applicationContext;
public static void setApplicationContext(ApplicationContext applicationContext) {
SpringBootModule.applicationContext = applicationContext;
}
3.3、SpringBootApplicationContextFactory的作用就是去掉xml的配置,避免启动出错
public class SpringBootApplicationContextFactory implements ApplicationContextFactory {
private static ConfigurableApplicationContext configurableApplicationContext ;
public ConfigurableApplicationContext createApplicationContext(List<String> locations) {
return configurableApplicationContext;
}
public static void setConfigurableApplicationContext(ConfigurableApplicationContext configurableApplicationContext) {
SpringBootApplicationContextFactory.configurableApplicationContext = configurableApplicationContext;
}
}
这里的setConfigurableApplicationContext方法也可以设置applicationContext。
3.4、结合spring和unitils的classRunner:UnitilsBootBlockJUnit4ClassRunner
这个类是结合了原始unitils中的UnitilsBlockJUnit4ClassRunner和spring-test中的SpringJUnit4ClassRunner,使其在初始化spring容器后,还可以unitils相关的初始化。
3.5、添加xls文件支持:MultiSchemaXlsDataSetReader和MultiSchemaXlsDataSetFactory
MultiSchemaXlsDataSetReader和MultiSchemaXlsDataSetFactory添加后,就可以支持@DataSet中的xls文件。
3.6、替换datasource:DataSourcePostProcessor
测试时,替换容器中的datasource,使用unitils来进行事务管理
@Component
public class DataSourcePostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("dataSource")) {
try {
return new UnitilsDataSourceFactoryBean().getObject();
} catch (Exception exp) {
throw new RuntimeException("replace database throw exception ,can not continue to process", exp);
}
}
return bean;
}
}
三、测试样例
1、unitils.properties配置文件
unitils.modules=database,dbunit,springBoot
unitils.module.springBoot.className=com.unitils.boot.SpringBootModule
unitils.module.springBoot.runAfter=database
unitils.module.springBoot.enabled=true
#自扩展模块
unitils.module.dbunit.className=org.unitils.dbunit.DbUnitModule
############################################################################
### Database模块相应配置 ###
############################################################################
## Full qualified class name of an implementation of org.unitils.database.datasource.DataSourceFactory. This class is used
# to provide a DataSource for all database unit tests and for the DBMaintainer.
org.unitils.database.datasource.DataSourceFactory.implClassName=org.unitils.database.datasource.impl.DefaultDataSourceFactory
#数据库事务类型
#可选:commit/rollback/disanled
database.default.transaction.mode=commit
## 测试数据库
# 此数据库驱动类型
database.driverClassName=com.mysql.jdbc.Driver
# 此数据库连接信息
database.url=jdbc:mysql://127.0.0.1/test
# 此数据库连接用户名
database.userName=root
# 此数据库连接用户密码
database.password=12345678
# 此数据库连接的schema
database.schemaNames=test
# 此数据库数据库类型:oracle/mysql/postgres等
database.dialect=mysql
# 不同数据库对应的实现
# Fully qualified classnames of the different, dbms specific implementations of org.dbmaintain.database.Database.implClassName
org.dbmaintain.database.Database.implClassName.oracle=org.dbmaintain.database.impl.OracleDatabase
org.dbmaintain.database.Database.implClassName.mysql=org.dbmaintain.database.impl.MySqlDatabase
# 是否支持初数据库始化脚本,默认关闭(可以通过脚本每次重建数据库等)
# The database maintainer is disabled by default.
updateDataBaseSchema.enabled=true
#This table is by default not created automatically
dbMaintainer.autoCreateExecutedScriptsTable=true
# Indicates whether a from scratch update should be performed when the previous update failed, but
# none of the scripts were modified since that last update. If false a new update will be tried only when
# changes were made to the script files.
dbMaintainer.keepRetryingAfterError.enabled=true
dbMaintainer.script.locations=
############################################################################
### Database模块相应配置 ###
############################################################################
# Dbunit中DataSet和ExpectedDataSet的数据准备实现类,(也可以用Excel准备数据,需要替换实现类)
DbUnitModule.DataSet.factory.default=com.unitils.boot.xls.MultiSchemaXlsDataSetFactory
DbUnitModule.ExpectedDataSet.factory.default=com.unitils.boot.xls.MultiSchemaXlsDataSetFactory
org.dbunit.database.IMetadataHandler.implClassName=org.dbunit.ext.mysql.MySqlMetadataHandler
## Dbunit中测试数据处理策略
# CleanInsertLoadStrategy:先删除dateSet中有关表的数据,然后再插入数据。
# InsertLoadStrategy:只插入数据。
# RefreshLoadStrategy:有同样key的数据更新,没有的插入。
# UpdateLoadStrategy: 有同样key的数据更新,没有的不做任何操作。
DbUnitModule.DataSet.loadStrategy.default=org.unitils.dbunit.datasetloadstrategy.impl.CleanInsertLoadStrategy
# XSD generator
dataSetStructureGenerator.xsd.dirName=/tmp/resources/xsd
SpringModule.applicationContextFactory.implClassName=com.unitils.boot.util.SpringBootApplicationContextFactory
module的配置:
unitils.modules=database,dbunit,springBoot
unitils.module.springBoot.className=com.unitils.boot.SpringBootModule
第一行指定了module,springBoot是spring-boot-unitils-starter中的,unitils.module.springBoot.className指定了其对应的类。
database.driverClassName=com.mysql.jdbc.Driver
# 此数据库连接信息
database.url=jdbc:mysql://127.0.0.1/test
# 此数据库连接用户名
database.userName=root
# 此数据库连接用户密码
database.password=12345678
# 此数据库连接的schema
database.schemaNames=test
# 此数据库数据库类型:oracle/mysql/postgres等
database.dialect=mysql
上述指定了单元测试数据库,一般只有这里根据自己的情况修改。
DbUnitModule.DataSet.factory.default=com.unitils.boot.xls.MultiSchemaXlsDataSetFactory
DbUnitModule.ExpectedDataSet.factory.default=com.unitils.boot.xls.MultiSchemaXlsDataSetFactory
SpringModule.applicationContextFactory.implClassName=com.unitils.boot.util.SpringBootApplicationContextFactory
上述指定了@DataSet、@ExpectedDataSet的文件解析和applicationContextFactory的类位置,这里如果是xls形式的数据文件,则不用修改,如果是xml形式的数据,只需要修改如下的配置值即可
DbUnitModule.DataSet.factory.default=com.unitils.boot.xls.MultiSchemaXlsDataSetFactory
DbUnitModule.ExpectedDataSet.factory.default=com.unitils.boot.xls.MultiSchemaXlsDataSetFactory
2、测试主类
package com.unitils.boot.controller;
import com.unitils.boot.SampleTestApplication;
import com.unitils.boot.util.UnitilsBootBlockJUnit4ClassRunner;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.unitils.database.annotations.Transactional;
import org.unitils.database.util.TransactionMode;
import org.unitils.dbunit.annotation.DataSet;
import org.unitils.spring.annotation.SpringBeanByType;
@RunWith(UnitilsBootBlockJUnit4ClassRunner.class)
@SpringBootTest(classes = SampleTestApplication.class)
@Transactional(value = TransactionMode.ROLLBACK)
public class UserControllerTest {
@SpringBeanByType
private UserController userController ;
@Test
@DataSet(value = {"/data/getUserInfo.xls"})
public void test_getUsername(){
String username = userController.getUsername(3);
Assert.assertNotNull(username);
Assert.assertTrue(username.equals("wangwu"));
}
}
注解@RunWith指定了我们定制的ClassRunner:UnitilsBootBlockJUnit4ClassRunner,@SpringBootTest指定了spring-boot的应用启动类:SampleTestApplication,因为我们需要让spring容器识别到ConfigurableApplicationContextAware,因此,需要新建一个类来让ConfigurableApplicationContextAware(在包com.unitils.boot.autoconfigure下)被扫描到,这里新建了SampleTestApplication,其代码如下:
package com.unitils.boot;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan({"com.unitils.boot", "com.unitils.boot.autoconfigure"})
@MapperScan(basePackages = "com.unitils.boot.mapper")
public class SampleTestApplication {
public static void main(String[] args) {
SpringApplication.run(SampleTestApplication.class, args);
}
}
3、数据准备文件getUserInfo.xls
列名就是表的列名,sheet的名字就是表名。
四、扩展
项目只是在每次跑一个单元测试方法的时候用过,其他场景还没用过,不知道会出什么问题;项目也没经过完备的测试,可能存在未知BUG,后续后有针对性的完善。
项目已经经过测试,满足单元测试需求,在单个和多个测试同时跑的情况下,表现良好。
在此基础之上修改得到的成果是:spring-boot-unitils-starter可以在maven中央库下载了,对应的maven依赖如下:
<dependency>
<groupId>com.github.yangjianzhou</groupId>
<artifactId>spring-boot-unitils-starter</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
附:项目的完整代码见:https://github.com/yangjianzhou/spring-boot-unitils
来源:oschina
链接:https://my.oschina.net/u/779984/blog/1842466