對dbunit進行mybatis DAO層Excel單元測試(必看篇)
DAO層測試難點
可重復性,每次運行單元測試,得到的數(shù)據(jù)是重復的
獨立性,測試數(shù)據(jù)與實際數(shù)據(jù)相互獨立
數(shù)據(jù)庫中臟數(shù)據(jù)預(yù)處理
不能給數(shù)據(jù)庫中數(shù)據(jù)帶來變化
DAO層測試方法
使用內(nèi)存數(shù)據(jù)庫,如H2。優(yōu)點:無需清空無關(guān)數(shù)據(jù);缺點:單元測試中需要進行數(shù)據(jù)庫初始化過程,如果初始化過程復雜,單元測試工作量增大
使用dbunit。優(yōu)點:數(shù)據(jù)庫初始化簡單,大大減輕單元測試工作量;缺點:目前官方提供jar包只支持xml格式文件,需要自己開發(fā)Excel格式文件
基于dbunit進行DAO單元測試
應(yīng)用環(huán)境: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文件過濾規(guī)則,如果不過濾maven會對resource文件重編碼,導致Excel文件被破壞
<resources>
<resource>
<directory>src/test/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
2. unitils.properties
在測試源碼根目錄中創(chuàng)建一個項目級別的unitils.properties配置文件,主要用于配置自定義拓展模塊,數(shù)據(jù)加載等相關(guān)信息
#啟用unitils所需模塊 unitils.modules=database,dbunit #自定義擴展模塊,加載Excel文件,默認拓展模塊org.unitils.dbunit.DbUnitModule支持xml unitils.module.dbunit.className=org.agoura.myunit.module.MyDbUnitModule #配置數(shù)據(jù)庫連接 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 #配置為數(shù)據(jù)庫名稱 database.schemaNames=teams #配置數(shù)據(jù)庫方言 database.dialect=mysql #需設(shè)置false,否則我們的測試函數(shù)只有在執(zhí)行完函數(shù)體后,才將數(shù)據(jù)插入的數(shù)據(jù)表中 unitils.module.database.runAfter=false #配置數(shù)據(jù)庫維護策略.請注意下面這段描述 # 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 #配置數(shù)據(jù)庫表創(chuàng)建策略,是否自動建表以及建表sql腳本存放目錄 dbMaintainer.autoCreateExecutedScriptsTable=true dbMaintainer.keepRetryingAfterError.enabled=true dbMaintainer.script.locations=src/main/resources/dbscripts #dbMaintainer.script.fileExtensions=sql #數(shù)據(jù)集加載策略 #CleanInsertLoadStrategy:先刪除dateSet中有關(guān)表的數(shù)據(jù),然后再插入數(shù)據(jù) #InsertLoadStrategy:只插入數(shù)據(jù) #RefreshLoadStrategy:有同樣key的數(shù)據(jù)更新,沒有的插入 #UpdateLoadStrategy:有同樣key的數(shù)據(jù)更新,沒有的不做任何操作 DbUnitModule.DataSet.loadStrategy.default=org.unitils.dbunit.datasetloadstrategy.impl.CleanInsertLoadStrategy #配置數(shù)據(jù)集工廠,自定義 DbUnitModule.DataSet.factory.default=org.agoura.myunit.utils.MultiSchemaXlsDataSetFactory DbUnitModule.ExpectedDataSet.factory.default=org.agoura.myunit.utils.MultiSchemaXlsDataSetFactory #配置事務(wù)策略 commit、rollback 和disabled;或者在代碼的方法上標記@Transactional(value=TransactionMode.ROLLBACK) #commit 是單元測試方法過后提交事務(wù) #rollback 是回滾事務(wù) #disabled 是沒有事務(wù),默認情況下,事務(wù)管理是disabled DatabaseModule.Transactional.value.default=commit #配置數(shù)據(jù)集結(jié)構(gòu)模式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>
<!-- (事務(wù)管理)transaction manager, use JtaTransactionManager for global tx -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
dbunit執(zhí)行流程
dbunit通過@DataSet注解讀取模擬數(shù)據(jù)Excel文件,流程如下:
Excel文件 --> @DataSet --> DbUnitModule --> DataSetFactory --> 數(shù)據(jù)庫(MySql)
@DataSet:將指定路徑下Excel文件加載到DbUnitModule中
DbUnitModule:對傳入文件進行預(yù)處理,源代碼中對傳入的xml文件copy一份臨時文件,并將臨時文件交給DataSetFactory處理,處理完后再刪除臨時文件
DataSetFactory:將讀取的Excel數(shù)據(jù)轉(zhuǎn)換為MultiSchemaDataSet,準備放入數(shù)據(jù)庫中
由于原代碼DbUnitModule中只有對xml文件的預(yù)處理,而我們是要對Excel文件進行預(yù)處理,所以需要對DbUnitModule進行重寫。重寫內(nèi)容為:完善DbUnitDatabaseConnection連接;針對Excel文件,修改預(yù)處理實現(xiàn);修改文件處理后續(xù)操作。示例如下:
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預(yù)處理操作,將@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;
}
//調(diào)用DataSetFactory.createDataSet()向數(shù)據(jù)庫中注入Excel數(shù)據(jù)后,直接返回DataSet,不對DataSet執(zhí)行清零操作
@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("創(chuàng)建數(shù)據(jù)集失?。? + Arrays.toString(dataSetFiles), e);
}
}
public String getDataSetFileExtension() {
return "xls";
}
}
createDataSet()為自定義的數(shù)據(jù)集工廠MultiSchemaXlsDataSetFactory中的核心方法,主要是讀取傳入的Excel文件,將讀取數(shù)據(jù)寫入MutiSchemaXlsDataSet中。MultiSchemaXlsDataSetReader通過POI實現(xiàn)了讀取Excel數(shù)據(jù)功能,可以同時讀取多個數(shù)據(jù)集,也即多個模擬數(shù)據(jù)庫數(shù)據(jù)。
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("構(gòu)造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<>();
// 需要根據(jù)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示例如下:

應(yīng)數(shù)據(jù)庫表名,字段必須和數(shù)據(jù)庫表字段一一對應(yīng)。
測試結(jié)果

以上這篇對dbunit進行mybatis DAO層Excel單元測試(必看篇)就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java8中Stream?API的peek()方法詳解及需要注意的坑
這篇文章主要給大家介紹了關(guān)于Java8中Stream?API的peek()方法詳解及需要注意的坑,Java 中的 peek 方法是 Java 8 中的 Stream API 中的一個方法,它屬于中間操作,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2024-06-06
使用ObjectMapper把Json轉(zhuǎn)換為復雜的實體類
這篇文章主要介紹了使用ObjectMapper把Json轉(zhuǎn)換為復雜的實體類操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
Spring boot基于ScheduledFuture實現(xiàn)定時任務(wù)
這篇文章主要介紹了Spring boot基于ScheduledFuture實現(xiàn)定時任務(wù),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-06-06
使用restTemplate遠程調(diào)controller路徑取數(shù)據(jù)
這篇文章主要介紹了使用restTemplate遠程調(diào)controller路徑取數(shù)據(jù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
SpringBoot+Response如何統(tǒng)一返回result結(jié)果集
這篇文章主要介紹了SpringBoot+Response如何統(tǒng)一返回result結(jié)果集,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05

