玩转单元測试之DBUnit

Posted liguangsunls

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了玩转单元測试之DBUnit相关的知识,希望对你有一定的参考价值。

本文同一时候发表在:http://www.cnblogs.com/wade-xu/p/4547381.html 

DBunit 是一种扩展于JUnit的数据库驱动測试框架,它使数据库在測试过程之间处于一种已知状态。假设一个測试用例对数据库造成了破坏性影响,它能够帮助避免造成后面的測试失败或者给出错误结果。

尽管不是什么新奇货,但近期正好用到。就把学到的跟大家分享一下。

关键词:数据库层測试,DAO层測试,DBUnit教程,DBUnit入门。DBUnit实例,Sring中结合DBUnit对Dao层測试

 

复制代码
文件夹
   简单介绍
   前提条件
   Maven配置
   准备工作
   实例具体解释
       測试基类
       关于数据集
       Example 1 FlatXmlDataSet
       Example 2 ReplacementDataSet
       Example 3 XlsDataSet
       Example 4 QueryDataSet
       Example 5 other
   Troubleshooting
   參考
复制代码

 

简单介绍

DBunit通过维护真实数据库与数据集(IDataSet)之间的关系来发现与暴露測试过程中的问题。IDataSet 代表一个或多个表的数据。

此处IDataSet能够自建。能够由数据库导出,并以多种方式体现,xml文件、XLS文件和数据库查询数据等。

基于DBUnit 的測试的主要接口是IDataSet,能够将数据库模式的所有内容表示为单个IDataSet 实例。这些表本身由Itable 实例来表示。

IDataSet 的实现有非常多,每个都相应一个不同的数据源或载入机制。最经常使用的几种 IDataSet 实现为: 

FlatXmlDataSet :数据的简单平面文件 XML 表示 
QueryDataSet :用 SQL 查询获得的数据 
DatabaseDataSet :数据库表本身内容的一种表示 
XlsDataSet :数据的excel 表示

 

前提条件

  • JDK 1.7
  • Maven 3

 

Maven配置

pom里加入下面的dependencies

    <dependency>
        <groupId>org.dbunit</groupId>
        <artifactId>dbunit</artifactId>
        <version>2.5.1</version>
    </dependency>

 

实例具体解释

測试流程大概是这种。建立数据库连接-> 备份表 -> 调用Dao层接口 -> 从数据库取实际结果-> 事先准备的期望结果 -> 断言 -> 回滚数据库 -> 关闭数据库连接

由于每一个測试都有非常多共性,所以提取成抽象基类例如以下。

測试基类:

复制代码
package com.demo.test.dao.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.dbunit.database.DatabaseConfig;
import org.dbunit.database.DatabaseConnection;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.database.QueryDataSet;
import org.dbunit.dataset.DataSetException;
import org.dbunit.dataset.DefaultDataSet;
import org.dbunit.dataset.DefaultTable;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ReplacementDataSet;
import org.dbunit.dataset.excel.XlsDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
import org.dbunit.ext.mysql.MySqlDataTypeFactory;
import org.dbunit.operation.DatabaseOperation;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;

/**
 * @Description: BaseDaoTest class
 * @author wadexu
 * 
 * @updateUser
 * @updateDate
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "file:src/test/resources/mvc-dispatcher-servlet.xml")
@TransactionConfiguration(defaultRollback = true)
public abstract class BaseDaoTest extends AbstractTransactionalJUnit4SpringContextTests {

    @Autowired
    private DataSource dataSource;

    private static IDatabaseConnection conn;

    private File tempFile;

    public static final String ROOT_URL = System.getProperty("user.dir") + "/src/test/resources/";

    @Before
    public void setup() throws Exception {
        //get DataBaseSourceConnection
        conn = new DatabaseConnection(DataSourceUtils.getConnection(dataSource));
        
        //config database as MySql
        DatabaseConfig dbConfig = conn.getConfig();
        dbConfig.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY,  new MySqlDataTypeFactory());
        
    }

    @After
    public void teardown() throws Exception {
        if (conn != null) {
            conn.close();
        }

    }

    /**
     * 
     * @Title: getXmlDataSet
     * @param name
     * @return
     * @throws DataSetException
     * @throws IOException
     */
    protected IDataSet getXmlDataSet(String name) throws DataSetException, IOException {
        FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();
        builder.setColumnSensing(true);
        return builder.build(new FileInputStream(new File(ROOT_URL + name)));
    }

    /**
     * Get DB DataSet
     * 
     * @Title: getDBDataSet
     * @return
     * @throws SQLException
     */
    protected IDataSet getDBDataSet() throws SQLException {
        return conn.createDataSet();
    }

    /**
     * Get Query DataSet
     * 
     * @Title: getQueryDataSet
     * @return
     * @throws SQLException
     */
    protected QueryDataSet getQueryDataSet() throws SQLException {
        return new QueryDataSet(conn);
    }

    /**
     * Get Excel DataSet
     * 
     * @Title: getXlsDataSet
     * @param name
     * @return
     * @throws SQLException
     * @throws DataSetException
     * @throws IOException
     */
    protected XlsDataSet getXlsDataSet(String name) throws SQLException, DataSetException,
            IOException {
        InputStream is = new FileInputStream(new File(ROOT_URL + name));

        return new XlsDataSet(is);
    }

    /**
     * backup the whole DB
     * 
     * @Title: backupAll
     * @throws Exception
     */
    protected void backupAll() throws Exception {
        // create DataSet from database.
        IDataSet ds = conn.createDataSet();

        // create temp file
        tempFile = File.createTempFile("temp", "xml");

        // write the content of database to temp file
        FlatXmlDataSet.write(ds, new FileWriter(tempFile), "UTF-8");
    }

    /**
     * back specified DB table
     * 
     * @Title: backupCustom
     * @param tableName
     * @throws Exception
     */
    protected void backupCustom(String... tableName) throws Exception {
        // back up specific files
        QueryDataSet qds = new QueryDataSet(conn);
        for (String str : tableName) {

            qds.addTable(str);
        }
        tempFile = File.createTempFile("temp", "xml");
        FlatXmlDataSet.write(qds, new FileWriter(tempFile), "UTF-8");

    }

    /**
     * rollback database
     * 
     * @Title: rollback
     * @throws Exception
     */
    protected void rollback() throws Exception {

        // get the temp file
        FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();
        builder.setColumnSensing(true);
        IDataSet ds =builder.build(new FileInputStream(tempFile));
        
        // recover database
        DatabaseOperation.CLEAN_INSERT.execute(conn, ds);
    }


    /**
     * Clear data of table
     * 
     * @param tableName
     * @throws Exception
     */
    protected void clearTable(String tableName) throws Exception {
        DefaultDataSet dataset = new DefaultDataSet();
        dataset.addTable(new DefaultTable(tableName));
        DatabaseOperation.DELETE_ALL.execute(conn, dataset);
    }

    /**
     * verify Table is Empty
     * 
     * @param tableName
     * @throws DataSetException
     * @throws SQLException
     */
    protected void verifyTableEmpty(String tableName) throws DataSetException, SQLException {
        Assert.assertEquals(0, conn.createDataSet().getTable(tableName).getRowCount());
    }

    /**
     * verify Table is not Empty
     * 
     * @Title: verifyTableNotEmpty
     * @param tableName
     * @throws DataSetException
     * @throws SQLException
     */
    protected void verifyTableNotEmpty(String tableName) throws DataSetException, SQLException {
        Assert.assertNotEquals(0, conn.createDataSet().getTable(tableName).getRowCount());
    }

    /**
     * 
     * @Title: createReplacementDataSet
     * @param dataSet
     * @return
     */
    protected ReplacementDataSet createReplacementDataSet(IDataSet dataSet) {
        ReplacementDataSet replacementDataSet = new ReplacementDataSet(dataSet);

        // Configure the replacement dataset to replace \'[NULL]\' strings with null.
        replacementDataSet.addReplacementObject("[null]", null);

        return replacementDataSet;
    }
}
复制代码

##转载注明出处: http://www.cnblogs.com/wade-xu/p/4547381.html 

 

我这里介绍的測试案例都是基于Spring项目的,假设是普通的项目,怎样配置数据库连接例如以下:

复制代码
public static void init() throws Exception {

        // get DataBaseSourceConnection
        testDataSource = new BasicDataSource();
        testDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        testDataSource.setUrl("jdbc:mysql://10.52.26.11:3306/Test?

useUnicode=true&characterEncoding=utf8&allowMultiQueries=true"); testDataSource.setUsername("xxx"); testDataSource.setPassword("xxxxx"); connection = new DatabaseDataSourceConnection(testDataSource); ImporterManager.setJdbcTemplate(new JdbcTemplate(testDataSource)); }

复制代码

 

关于数据集

DBUnit能够把全部表的记录存在一个数据集中:既能够是数据库中的表,也能够是文件里的数据。我们在此用FlatXmlDataSet来讲述。

在FlatXmlDataSet相应的XML文件中,元素名称相应数据库表名,元素的属性(attribute)相应表的列。如:

<dataset>
    <Person Name="Kirin" Age="31" Location="Beijing"/>
    <Person Name="Jade" Age="30"/>
</dataset>

要注意,假设数据库中某一条字段为null。在flat XML中将不会显示该attribute。

另外,FlatXmlDataSet用XML文件里该表的第一行数据来制定表的结构。因此,假设数据库中某个字段全部记录都为null,或者恰巧第一条记录为null,那么得到的表结构与原数据库的表结构就不一致了,測试就会失败。FlatXmlDataSet中存在一个column sensing的概念,在从文件载入数据时,将该属性设置为true。就会依据第一行展现出来的表结构。自己主动将别的行的列补齐。

顺便提一句,DBUnit中还存在还有一种格式的数据集XmlDataSet,在XmlDataSet相应的XML文件中,用元素的子元素相应表的列。如:

复制代码
<dataset>
    <Person>
        <Name>Kirin</Name>
        <Age>31</Age>
        <Location>Beijing</Location>
    </Person>
    <Person>
        <Name>Jade</Name>
        <Age>30</Age>
        <Location/>
    </Person>
</dataset>
复制代码

null的表示方法如红色部分。

##转载注明出处: http://www.cnblogs.com/wade-xu/p/4547381.html 

 

Example 1

关于FlatXmlDataSet

复制代码
package com.demo.test.dao.impl;

import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.dbunit.Assertion;
import org.dbunit.database.QueryDataSet;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.ReplacementDataSet;
import org.dbunit.dataset.excel.XlsDataSet;
import org.dbunit.dataset.filter.DefaultColumnFilter;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

import com.demo.test.dao.BundleDao;
import com.demo.test.dao.VersionDao;
import com.demo.test.entity.InfoEntity;
import com.demo.test.exception.DBException;/**
 * @Description: BundleDaoImpl Test via DBUnit
 * @author wadexu
 *
 * @updateUser
 * @updateDate
 */
public class BundleDaoImplDBUnitTest_Demo extends BaseDaoTest {

    @Autowired
    private BundleDao bundleDao;
    
    @Autowired
    private VersionDao versionDao;private static final String TABLE_DOCUMENTS_MASTER = "Documents_Master";
    private static final String TABLE_FILE_VERSION = "FILE_VERSION";
    private static final String VERSION_VALUE = "11.0.3";
    
    @Test
    public void testInsertBundles_1() throws Exception {
        backupCustom(TABLE_FILE_VERSION, TABLE_DOCUMENTS_MASTER);
        
        bundleDao.deleteAll();
       
        bundleDao.insertBundle(getBundles());
        
        //get actual tableInfo from DB
        IDataSet dbDataSet = getDBDataSet();
        ITable dbTable = dbDataSet.getTable(TABLE_DOCUMENTS_MASTER);
        
        //get expect Information from xml file
        IDataSet xmlDataSet = getXmlDataSet("expect_documents_master.xml");
        ITable xmlTable = xmlDataSet.getTable(TABLE_DOCUMENTS_MASTER);
        
        //exclude some columns which don\'t want to compare result
        dbTable = DefaultColumnFilter.excludedColumnsTable(dbTable, new String[]{"IndexId", "DM_VERSION_ID"});
        xmlTable = DefaultColumnFilter.excludedColumnsTable(xmlTable, new String[]{"IndexId", "DM_VERSION_ID"});
        
        Assertion.assertEquals(xmlTable, dbTable);
        
        rollback();
    }
}
复制代码

首先我备份了两张用到的表,当然也能够备份全部的表,基类都有写这些方法 (backupCustom, backupAll)

然后调用Dao层提供的方法。删除全部数据,接着插入Bundle数据, getBundles()是我的私有方法,构造insertBundle方法所需的数据

接下来,从DB里取实际数据, 用ITable的形式来表示表的实际内容

期望结果是从已准备好的xml文件读取, getxmlDataSet方法里用到了我上文所述的column sensing的概念, setColumnSensing=true, 前提是xml文件的第一行数据的列字段要全。和数据里的表结构一致。

<?

xml version=\'1.0\' encoding=\'UTF-8\'?

> <dataset> <Documents_Master IndexId="77" No="1" Retired="Y" GeneralCategory="Financial" DM_VERSION_ID="13" SOURCE="DBUnit"/> <Documents_Master IndexId="78" No="0" Retired="N" GeneralCategory="test" DM_VERSION_ID="13"/> </dataset>

在断言两张表之前,由于有些字段我不想比較,比方ID字段,它的值是动态的,无法事先定义好期望结果,所以能够用DefaultColumnFilter里的excludedColumnsTable方法来将指定字段给排除在比較范围之外。

相同还有includedColumnsTable方法能够指定想要比較的字段。

最后回滚数据库。

 

Example 2

假设插入数据库的数据非常多字段的值都是null, FlatXmlDataSet相应的XML文件中的数据该怎么定义第一行呢?

这时候ReplacementDataSet就能够登场了。

复制代码
   @Test
    public void testInsertBundles_2() throws Exception {
        backupCustom(TABLE_FILE_VERSION, TABLE_DOCUMENTS_MASTER);
        
        bundleDao.deleteAll();
       
        bundleDao.insertBundle(getBundles());
        
        //get actual tableInfo from DB
        IDataSet dbDataSet = getDBDataSet();
        ITable dbTable = dbDataSet.getTable(TABLE_DOCUMENTS_MASTER);
        
        //get expect Information from xml file
        IDataSet xmlDataSet = getXmlDataSet("expect_documents_master_2.xml");
        // handle null value, replace "[null]" strings with null
        ReplacementDataSet replacementDataSet = createReplacementDataSet(xmlDataSet);
        ITable xmlTable = replacementDataSet.getTable(TABLE_DOCUMENTS_MASTER);
        
        //exclude some columns which don\'t want to compare result
        dbTable = DefaultColumnFilter.excludedColumnsTable(dbTable, new String[]{"IndexId", "DM_VERSION_ID"});
        xmlTable = DefaultColumnFilter.excludedColumnsTable(xmlTable, new String[]{"IndexId", "DM_VERSION_ID"});
        
        Assertion.assertEquals(xmlTable, dbTable);
        
        rollback();
    }
复制代码

我的expect_documents_master_2.xml 文件例如以下:

<?xml version=\'1.0\' encoding=\'UTF-8\'?

> <dataset> <Documents_Master IndexId="77" No="1" Retired="Y" GeneralCategory="[null]" DM_VERSION_ID="13" SOURCE="[null]"/> <Documents_Master IndexId="78" No="0" Retired="N" DM_VERSION_ID="13"/> </dataset>

空元素的字段须要一个"[null]"占位符,然后用 replacementDataSet.addReplacementObject("[null]", null) 替换成null, 详见基类BaseDaoTest里的方法createReplacementDataSet.

 

Example 3

关于XlsDataSet

复制代码
 @Test
    public void testInsertBundles_Excel() throws Exception {
        backupCustom(TABLE_FILE_VERSION, TABLE_DOCUMENTS_MASTER);
        
        bundleDao.deleteAll();
        bundleDao.insertBundle(getBundles());
//get actual tableInfo from DB
        IDataSet dbDataSet = getDBDataSet();
        ITable dbTable = dbDataSet.getTable(TABLE_DOCUMENTS_MASTER);
        
        //get expect result from xls file
        XlsDataSet xlsDataSet = getXlsDataSet("expect_documents_master.xls");
        // table name is sheet name
        ITable xlsTable = xlsDataSet.getTable("Sheet1");

        //以上是关于玩转单元測试之DBUnit的主要内容,如果未能解决你的问题,请参考以下文章

ios单元測试之GHUnit

云计算系统測试之技术概念

熊猫猪新系统測试之四:Ubuntu 14.04

熊猫猪新系统測试之二:Mac OS X 10.10 优胜美地

依赖数据库的单元测试——DBUnit

执行单元测试时如何防止dbunit生成database.script文件