给spring-boot测试提供unitils支持的开源项目

不羁岁月 提交于 2019-11-26 16:42:42

一、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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!