文档章节

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

yangjianzhou
 yangjianzhou
发布于 2018/07/09 01:33
字数 1804
阅读 420
收藏 12

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

© 著作权归作者所有

yangjianzhou
粉丝 21
博文 19
码字总数 29460
作品 0
闵行
程序员
私信 提问
spring boot测试

大家好,目前在测试spring boot项目时,要么就是指定一个测试数据库,要么就是使用内存数据库, 但是这里有一个共同的问题:如果是内存数据库,无法查看测试后的结果,即测试运行后,数据库里...

yangjianzhou
2018/05/26
215
3
spring-boot-unitils-starter完全spring boot化

针对配置的问题进行改进,使其完全遵循spring boot规则 一、问题 在上一篇文章中,我们的配置都是放在unitils.properties,如下: 这里面既包含固定配置(例如,指定module、DataSet的数据加...

yangjianzhou
2018/07/10
0
0
单元测试维护工具--Unitils

Unitils这个Java开源类包的目的是让单元测试变得更加容易和可维护。Unitils构建在DBUnit与EasyMock项目之上并与 JUnit和TestNG相结合。支持数据库测试,支持利用mock对象进行测试并提供与Spr...

匿名
2008/10/05
2.2K
0
【让开发自动化】Unitils常见问题

Unitils是一个非常好用的集成了多个测试组件的工具,但是在使用过程中也遇到了一些问题,特记录如下: 仅支持FlatXmlDataSet,不支持一个表有多条记录的情况;通过自己实现DataSetFactory支持...

空心大白菜
2013/08/07
0
0
Spring mvc下的单元测试

最近一直在研究在spring mvc下应用单元测试,起初致力于Unitils+Junit+Dbunit的整合测试,原因有以下几点: Dbunit的可以方便地从excel或xml文件(数据集)中加载数据到数据库,然后经过测试...

chace0120
2014/04/07
0
0

没有更多内容

加载失败,请刷新页面

加载更多

容器中的JVM资源该如何被安全的限制?

前言 Java与Docker的结合,虽然更好的解决了application的封装问题。但也存在着不兼容,比如Java并不能自动的发现Docker设置的内存限制,CPU限制。 这将导致JVM不能稳定服务业务!容器会杀死你...

xiaomin0322
27分钟前
3
0
mysql查询最近连续登录和累计登录

这条sql写了一天,百度无数,终于摸到点门路 需求是查询从当前日期向前推的连续登录,比如一个用户他今天登录了,昨天没登,连续登录为1 他昨天前天都登录了,今天没登录,连续登录为0 SELEC...

七月大人
29分钟前
1
0
常用的一些Vip解析

无广告解析推荐(排行不分前后) 黑米免费解析   https://www.myxin.top/jx/api/?url= 随缘免费解析   http://www.syhbyl.tw/jx/api/?url= 快快免费解析   http://jx.kkqtv.com/jx/...

chenhongjiang
29分钟前
2
0
Netty 整合spring bean注入失败。

1.Netty整合spring bean注入为null的问题,自己折腾了好久,试过各种方式(@Component,@PostConstruct)这些都不行。 2.最后发现了关键的问题就是添加的回调类(处理类)/初始化的类也必须由s...

轻量级赤影
34分钟前
7
0
消息中间件

本文大概围绕如下几点进行阐述: 为什么使用消息队列? 使用消息队列有什么缺点? 消息队列如何选型? 如何保证消息队列是高可用的? 如何保证消息不被重复消费? 如何保证消费的可靠性传输? 如何...

石日天
36分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部