基於dbunit進行mybatis DAO層Excel單元測試

DAO層測試難點

  1. 可重複性,每次運行單元測試,獲得的數據是重複的
  2. 獨立性,測試數據與實際數據相互獨立
  3. 數據庫中髒數據預處理
  4. 不能給數據庫中數據帶來變化

DAO層測試方法

  1. 使用內存數據庫,如H2。優勢:無需清空無關數據;缺點:單元測試中須要進行數據庫初始化過程,若是初始化過程複雜,單元測試工做量增大
  2. 使用dbunit。優勢:數據庫初始化簡單,大大減輕單元測試工做量;缺點:目前官方提供jar包只支持xml格式文件,須要本身開發Excel格式文件

基於dbunit進行DAO單元測試

應用環境:Spring、Mybatis、MySql、Exceljava

配置文件

1. pom.xml

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

<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文件被破壞spring

<resources>
        <resource>
            <directory>src/test/resources</directory>
            <includes>
                <include>**/*.*</include>
            </includes>
            <filtering>false</filtering>
        </resource>
    </resources>

2. unitils.properties

在測試源碼根目錄中建立一個項目級別的unitils.properties配置文件,主要用於配置自定義拓展模塊,數據加載等相關信息sql

#啓用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&amp;useUnicode=true&amp;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,準備放入數據庫中mybatis

因爲原代碼DbUnitModule中只有對xml文件的預處理,而咱們是要對Excel文件進行預處理,因此須要對DbUnitModule進行重寫。重寫內容爲:完善DbUnitDatabaseConnection鏈接;針對Excel文件,修改預處理實現;修改文件處理後續操做。示例以下:app

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進行重寫。示例以下:maven

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數據功能,能夠同時讀取多個數據集,也即多個模擬數據庫數據。ide

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

應數據庫表名,字段必須和數據庫表字段一一對應。

測試結果

相關文章
相關標籤/搜索