对dbunit进行mybatis DAO层Excel单元测试(必看篇)

网友投稿 509 2023-05-13


对dbunit进行mybatis DAO层Excel单元测试(必看篇)

DAO层测试难点

可重复性,每次运行单元测试,得到的数据是重复的

独立性,测试数据与实际数据相互独立

数据库中脏数据预处理

不能给数据库中数据带来变化

DAO层测试方法

使用内存数据库,如H2。优点:无需清空无关数据;缺点:单元测试中需要进行数据库初始化过程,如果初始化过程复杂,单元测试工作量增大

使用dbunit。优点:数据库初始化简单,大大减轻单元测试工作量;缺点:目前官方提供jar包只支持xml格式文件,需要自己开发Excel格式文件

基于dbunit进行DAO单元测试

应用环境:Spring、Mybatis、mysql、Excel

配置文件

1. pom.xml

引入jar包,unitils整合了dbunit,database,spring,io等模块

org.unitils

unitils-core

3.4.2

org.unitils

unitils-dbunit

3.4.2

org.unitils

unitils-io

3.4.2

org.unitils

unitils-database

3.4.2

org.unitils

unitils-spring

3.4.2

org.dbunit

dbunit

2.5.3

配置maven对resourcew文件过滤规则,如果不过滤maven会对resource文件重编码,导致Excel文件被破坏

src/test/resources

**/*.*

false

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

xmlns:context="http://springframework.org/schema/context"

xmlns="http://springframework.org/schema/beans"

xsi:schemaLocation="http://springframework.org/schema/beans

http://springframework.org/schema/beans/spring-beans-3.1.xsd

http://springframework.org/schema/context

http://springframework.org/schema/context/spring-context-3.1.xsd">

class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

xmlns:context="http://springframework.org/schema/context"

xmlns="http://springframework.org/schema/beans"

xsi:schemaLocation="http://springframework.org/schema/beans

http://springframework.org/schema/beans/spring-beans-3.1.xsd

http://springframework.org/schema/context

http://springframework.org/schema/context/spring-context-3.1.xsd">

class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

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 dataSetFiles = new ArrayList();

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 xlsDataSetRehttp://ader.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> tbMap = getTables(dataSetFiles);

MultiSchemaDataSet dataSets = new MultiSchemaDataSet();

for (Map.Entry> entry : tbMap.entrySet()) {

List 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> getTables(File... dataSetFiles) {

Map> 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());

}

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示例如下:

应数据库表名,字段必须和数据库表字段一一对应。

测试结果


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:angular+webpack2实战例子
下一篇:vue2.0 与 bootstrap datetimepicker的结合使用实例
相关文章

 发表评论

暂时没有评论,来抢沙发吧~