对dbunit进行mybatis DAO层Excel单元测试(必看篇)
作者:jingxian 发布时间:2023-08-19 02:37:19
DAO层测试难点
可重复性,每次运行单元测试,得到的数据是重复的
独立性,测试数据与实际数据相互独立
数据库中脏数据预处理
不能给数据库中数据带来变化
DAO层测试方法
使用内存数据库,如H2。优点:无需清空无关数据;缺点:单元测试中需要进行数据库初始化过程,如果初始化过程复杂,单元测试工作量增大
使用dbunit。优点:数据库初始化简单,大大减轻单元测试工作量;缺点:目前官方提供jar包只支持xml格式文件,需要自己开发Excel格式文件
基于dbunit进行DAO单元测试
应用环境:Spring、Mybatis、MySql、Excel
配置文件
1. pom.xml
引入jar包,unitils整合了dbunit,database,spring,io等模块
<dependency>
<groupId>org.unitils</groupId>
<artifactId>unitils-core</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.unitils</groupId>
<artifactId>unitils-dbunit</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.unitils</groupId>
<artifactId>unitils-io</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.unitils</groupId>
<artifactId>unitils-database</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.unitils</groupId>
<artifactId>unitils-spring</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.dbunit</groupId>
<artifactId>dbunit</artifactId>
<version>2.5.3</version>
</dependency>
配置maven对resourcew文件过滤规则,如果不过滤maven会对resource文件重编码,导致Excel文件被破坏
<resources>
<resource>
<directory>src/test/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
2. unitils.properties
在测试源码根目录中创建一个项目级别的unitils.properties配置文件,主要用于配置自定义拓展模块,数据加载等相关信息
#启用unitils所需模块
unitils.modules=database,dbunit
#自定义扩展模块,加载Excel文件,默认拓展模块org.unitils.dbunit.DbUnitModule支持xml
unitils.module.dbunit.className=org.agoura.myunit.module.MyDbUnitModule
#配置数据库连接
database.driverClassName=com.mysql.jdbc.Driver
database.url=jdbc:mysql://127.0.0.1:3306/teams?autoReconnect=true&useUnicode=true&characterEncoding=utf-8
database.userName=root
database.password=agoura
#配置为数据库名称
database.schemaNames=teams
#配置数据库方言
database.dialect=mysql
#需设置false,否则我们的测试函数只有在执行完函数体后,才将数据插入的数据表中
unitils.module.database.runAfter=false
#配置数据库维护策略.请注意下面这段描述
# If set to true, the DBMaintainer will be used to update the unit test database schema. This is done once for each
# test run, when creating the DataSource that provides access to the unit test database.
updateDataBaseSchema.enabled=true
#配置数据库表创建策略,是否自动建表以及建表sql脚本存放目录
dbMaintainer.autoCreateExecutedScriptsTable=true
dbMaintainer.keepRetryingAfterError.enabled=true
dbMaintainer.script.locations=src/main/resources/dbscripts
#dbMaintainer.script.fileExtensions=sql
#数据集加载策略
#CleanInsertLoadStrategy:先删除dateSet中有关表的数据,然后再插入数据
#InsertLoadStrategy:只插入数据
#RefreshLoadStrategy:有同样key的数据更新,没有的插入
#UpdateLoadStrategy:有同样key的数据更新,没有的不做任何操作
DbUnitModule.DataSet.loadStrategy.default=org.unitils.dbunit.datasetloadstrategy.impl.CleanInsertLoadStrategy
#配置数据集工厂,自定义
DbUnitModule.DataSet.factory.default=org.agoura.myunit.utils.MultiSchemaXlsDataSetFactory
DbUnitModule.ExpectedDataSet.factory.default=org.agoura.myunit.utils.MultiSchemaXlsDataSetFactory
#配置事务策略 commit、rollback 和disabled;或者在代码的方法上标记@Transactional(value=TransactionMode.ROLLBACK)
#commit 是单元测试方法过后提交事务
#rollback 是回滚事务
#disabled 是没有事务,默认情况下,事务管理是disabled
DatabaseModule.Transactional.value.default=commit
#配置数据集结构模式XSD生成路径,可以自定义目录,但不能为空
dataSetStructureGenerator.xsd.dirName=src/main/resources/xsd
dbMaintainer.generateDataSetStructure.enabled=true
#文件相对路径是否是测试类文件路径,false表示resource根目录
dbUnit.datasetresolver.prefixWithPackageName=false
3. spring-mybatis-unitils.xml
<?xml version="1.0" encoding="GBK"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<context:component-scan base-package="com.agoura.agoura"/>
<context:property-placeholder location="classpath:jdbc_dbcp.properties"/>
<!--<util:properties id="jdbc_dbcp" />-->
<bean id="dataSource" class="org.unitils.database.UnitilsDataSourceFactoryBean"/>
<!-- spring和MyBatis整合,不需要mybatis的配置映射文件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath*:com/agoura/agoura/mapper/xml/*.xml"></property>
</bean>
<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.agoura.agoura.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
<!-- (事务管理)transaction manager, use JtaTransactionManager for global tx -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
dbunit执行流程
dbunit通过@DataSet注解读取模拟数据Excel文件,流程如下:
Excel文件 --> @DataSet --> DbUnitModule --> DataSetFactory --> 数据库(MySql)
@DataSet:将指定路径下Excel文件加载到DbUnitModule中
DbUnitModule:对传入文件进行预处理,源代码中对传入的xml文件copy一份临时文件,并将临时文件交给DataSetFactory处理,处理完后再删除临时文件
DataSetFactory:将读取的Excel数据转换为MultiSchemaDataSet,准备放入数据库中
由于原代码DbUnitModule中只有对xml文件的预处理,而我们是要对Excel文件进行预处理,所以需要对DbUnitModule进行重写。重写内容为:完善DbUnitDatabaseConnection连接;针对Excel文件,修改预处理实现;修改文件处理后续操作。示例如下:
import org.dbunit.database.DatabaseConfig;
import org.dbunit.ext.mysql.MySqlDataTypeFactory;
import org.dbunit.ext.mysql.MySqlMetadataHandler;
import org.unitils.core.UnitilsException;
import org.unitils.dbmaintainer.locator.ClassPathDataLocator;
import org.unitils.dbmaintainer.locator.resourcepickingstrategie.ResourcePickingStrategie;
import org.unitils.dbunit.DbUnitModule;
import org.unitils.dbunit.datasetfactory.DataSetFactory;
import org.unitils.dbunit.util.DbUnitDatabaseConnection;
import org.unitils.dbunit.util.MultiSchemaDataSet;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class MyDbUnitModule extends DbUnitModule {
//完善DbUnitDatabaseConnection连接信息
@Override
public DbUnitDatabaseConnection getDbUnitDatabaseConnection(final String schemaName) {
DbUnitDatabaseConnection result = dbUnitDatabaseConnections.get(schemaName);
if (result != null) {
return result;
}
result = super.getDbUnitDatabaseConnection(schemaName);
result.getConfig().setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new MySqlDataTypeFactory());
result.getConfig().setProperty(DatabaseConfig.PROPERTY_METADATA_HANDLER, new MySqlMetadataHandler());
return result;
}
//Excel预处理操作,将@DataSet注释读取的文件返回给DataSetFactory进行处理
@Override
protected File handleDataSetResource(ClassPathDataLocator locator, String nameResource, ResourcePickingStrategie strategy, Class<?> testClass) {
String cloneResource = new String(nameResource);
String packageName = testClass.getPackage() != null?testClass.getPackage().getName():"";
String tempName = "";
if(cloneResource.startsWith(packageName.replace(".", "/"))) {
cloneResource = tempName = cloneResource.substring(packageName.length());
} else if(cloneResource.startsWith(packageName)) {
cloneResource = tempName = cloneResource.substring(packageName.length() + 1);
} else {
tempName = cloneResource;
}
InputStream in = locator.getDataResource(packageName.replace(".", "/") + "/" + tempName, strategy);
File resolvedFile = null;
if(in == null) {
resolvedFile = this.getDataSetResolver().resolve(testClass, cloneResource);
if(resolvedFile == null) {
throw new UnitilsException("DataSetResource file with name '" + nameResource + "' cannot be found");
}
}
return resolvedFile;
}
//调用DataSetFactory.createDataSet()向数据库中注入Excel数据后,直接返回DataSet,不对DataSet执行清零操作
@Override
protected MultiSchemaDataSet getDataSet(Class<?> testClass, String[] dataSetFileNames, DataSetFactory dataSetFactory) {
List<File> dataSetFiles = new ArrayList<File>();
ResourcePickingStrategie resourcePickingStrategie = getResourcePickingStrategie();
for (String dataSetFileName : dataSetFileNames) {
File dataSetFile = handleDataSetResource(new ClassPathDataLocator(), dataSetFileName, resourcePickingStrategie, testClass);
dataSetFiles.add(dataSetFile);
}
MultiSchemaDataSet dataSet = dataSetFactory.createDataSet(dataSetFiles.toArray(new File[dataSetFiles.size()]));
return dataSet;
}
}
拓展模块DbUnitModule重写完后,由于官方版本中DataSetFactory只对xml文件进行处理,为了能处理Excel文件,需要对DataSetFactory进行重写。示例如下:
import org.unitils.core.UnitilsException;
import org.unitils.dbunit.datasetfactory.DataSetFactory;
import org.unitils.dbunit.util.MultiSchemaDataSet;
import java.io.File;
import java.util.*;
public class MultiSchemaXlsDataSetFactory implements DataSetFactory {
protected String defaultSchemaName;
public void init(Properties configuration, String s) {
this.defaultSchemaName = s;
}
public MultiSchemaDataSet createDataSet(File... dataSetFiles) {
try {
MultiSchemaXlsDataSetReader xlsDataSetReader = new MultiSchemaXlsDataSetReader(defaultSchemaName);
return xlsDataSetReader.readDataSetXls(dataSetFiles);
} catch (Exception e) {
throw new UnitilsException("创建数据集失败:" + Arrays.toString(dataSetFiles), e);
}
}
public String getDataSetFileExtension() {
return "xls";
}
}
createDataSet()为自定义的数据集工厂MultiSchemaXlsDataSetFactory中的核心方法,主要是读取传入的Excel文件,将读取数据写入MutiSchemaXlsDataSet中。MultiSchemaXlsDataSetReader通过POI实现了读取Excel数据功能,可以同时读取多个数据集,也即多个模拟数据库数据。
import org.dbunit.database.AmbiguousTableNameException;
import org.dbunit.dataset.DefaultDataSet;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.excel.XlsDataSet;
import org.unitils.core.UnitilsException;
import org.unitils.dbunit.util.MultiSchemaDataSet;
import java.io.File;
import java.io.FileInputStream;
import java.util.*;
public class MultiSchemaXlsDataSetReader {
private String pattern = ".";
private String defaultSchemaName;
public MultiSchemaXlsDataSetReader(String defaultSchemaName) {
this.defaultSchemaName = defaultSchemaName;
}
public MultiSchemaDataSet readDataSetXls(File... dataSetFiles) {
try {
Map<String, List<ITable>> tbMap = getTables(dataSetFiles);
MultiSchemaDataSet dataSets = new MultiSchemaDataSet();
for (Map.Entry<String, List<ITable>> entry : tbMap.entrySet()) {
List<ITable> tables = entry.getValue();
try {
DefaultDataSet ds = new DefaultDataSet(tables.toArray(new ITable[]{}));
dataSets.setDataSetForSchema(entry.getKey(), ds);
} catch (AmbiguousTableNameException e) {
throw new UnitilsException("构造DataSet失败!", e);
}
}
return dataSets;
} catch (Exception e) {
throw new UnitilsException("解析Excel文件出错:", e);
}
}
private Map<String, List<ITable>> getTables(File... dataSetFiles) {
Map<String, List<ITable>> tableMap = new HashMap<>();
// 需要根据schema把Table重新组合一下
try {
String schema, tableName;
for (File file : dataSetFiles) {
IDataSet dataSet = new XlsDataSet(new FileInputStream(file));
String[] tableNames = dataSet.getTableNames();
for (String tn : tableNames) {
String[] temp = tn.split(pattern);
if (temp.length == 2) {
schema = temp[0];
tableName = temp[1];
} else {
schema = this.defaultSchemaName;
tableName = tn;
}
ITable table = dataSet.getTable(tn);
if (!tableMap.containsKey(schema)) {
tableMap.put(schema, new ArrayList<ITable>());
}
tableMap.get(schema).add(new XslTableWrapper(tableName, table));
}
}
} catch (Exception e) {
throw new UnitilsException("Unable to create DbUnit dataset for data set files: " + Arrays.toString(dataSetFiles), e);
}
return tableMap;
}
}
到此,unitils重写及配置完毕,下面进行测试。
测试示例
被测试DAO层代码:
public interface MembersMapper {
int deleteByPrimaryKey(Integer id);
int insert(Members record);
Members selectByPrimaryKey(Integer id);
int updateByPrimaryKey(Members record);
}
测试类文件:
import com.agoura.entity.Members;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.unitils.UnitilsJUnit4;
import org.unitils.UnitilsJUnit4TestClassRunner;
import org.unitils.dbunit.annotation.DataSet;
import static org.junit.Assert.assertNotNull;
@RunWith(UnitilsJUnit4TestClassRunner.class)
@ContextConfiguration(locations = {"classpath*:spring-*.xml"})
public class MembersMapperTest extends UnitilsJUnit4 {
private MembersMapper membersMapper;
private static ApplicationContext ctx;
@BeforeClass
public static void setUpBeforeClass() {
ctx = new ClassPathXmlApplicationContext("classpath*:spring-mybatis-unitils.xml");
}
@Before
public void setUp() {
membersMapper = (MembersMapper) ctx.getBean("membersMapper");
}
@Test
@DataSet(value = {"test.xls"}) //test.xlsx
public void testSelectByPrimaryKey() throws Exception {
Members member = membersMapper.selectByPrimaryKey(3);
System.out.println(member);
assertEquals("王五", member.getName());
}
}
@DataSet加载Excel文件,既可以加载 .xls文件,也可以加载 .xlsx文件。
.xls示例如下:
应数据库表名,字段必须和数据库表字段一一对应。
测试结果


猜你喜欢
- 一、什么是SFTP?SFTP是一个安全文件传送协议,可以为传输文件提供一种安全的加密方法。SFTP 为 SSH的一部份,是一种传输文件到服务
- 碰到一个项目,需要对指定的网页进行截图保存,晕死! 需求永远都是怪异的..... 解决是关键~ 遂写了以下代码,快准狠!(因为赶时间!) 可
- 最近安装了idea,觉得比eclipse好用很多,今天不知道为啥yml文件就不识别了,上面显示一个问号,我查了半天,解决办法就是安装一个插件
- 在开发时,手机先要ROOT,然后在通过代码改变权限。<span style="color:#330033;">
- 使用AES算法可用于对数据进行加密码与解密,使用的时候需要注意两点:1)被加密的串越长,加密后的字符串越长,注意数据库字段的设计;2)Lin
- 多态性1理解多态性:可以理解为一个事物的多种形态。2何为多态性:对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)3多态的
- MyBatis简介MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有的JDBC代码和参
- 一、银行存取款1.前言毕竟谁不喜欢钱呢!(不是😅)我看谁不喜欢在知识的海洋中遨游😤!2.描述银行存取款的流程是人们非常熟悉的事情,用户可以在
- 条形码,是由宽度不等的多个黑条和空白所组成,用以表达一组信息的图形标识符。通过给文档添加条形码,可以直观,快捷地访问和分享一些重要的信息。本
- 配置两个parent的方法在向pom.xml 文件中添加依赖之前需要先添加spring-boot-starter-parent。spring
- 前言Mybatis是web工程开发中非常常用的数据持久化的框架,通过该框架,我们非常容易的进行数据库的增删改查。数据库连接进行事务提交的时候
- 项目初始流程:首先说一下pom.xml文件的依赖: <dependencies><!-- junit 测试 -->
- 计算器项目,要求实现加、减、乘、除、求倒数、求平方根等简单运算。真机调试结果如下图:布局文件:main_activity.xml<?x
- 这篇文章主要介绍了SPRING IOC注入方式过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋
- private static char[] constant = &
- 本文实例讲述了Android编程实现拍照功能的2种方法。分享给大家供大家参考,具体如下:Android系统的照相功能,已实现2种方法,可供大
- Java枚举类使用Lombok枚举类是一个特殊的常量类,由于其特殊的设计,具有简洁性、安全性以及便捷性,在开发中被普遍使用。本文简单介绍一下
- 1. 单个属性yaml 内的属性如下:server: port: 80只需在成员变量上注解 @Value(“
- Intro做项目的时候,页面上有一些敏感信息,需要用“*”隐藏一些比较重要的信息,于是打算写一个通用的方法。Let's do it
- 最近项目上产品经理提了个需求,要求关闭语言国际化,不管手机系统设置那个国家的语言,都要显示汉语,好吧,既然有需求,那就做吧。但是项目中已经有