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

官方使用指南:http://dbunit.sourceforge.net/howto.html

参考资料:https://www.cnblogs.com/wade-xu/p/4547381.html

码云代码(在Spring Cloud配置中心项目基础上):https://gitee.com/wudiyong/ConfigServer.git


介绍

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


DbUnit has the ability to export and import your database data to and from XML datasets. Since version 2.0, DbUnit can also work with very large datasets when used in streaming mode. DbUnit can also help you to verify that your database data match an expected set of values.


DBunit通过维护真实数据库与数据集(IDataSet)之间的关系来发现与暴露测试过程中的问题。IDataSet 代表一个或多个表的数据。此处IDataSet可以自建,可以由数据库导出,并以多种方式体现,xml文件、XLS文件和数据库查询数据等。


基于DBUnit 的测试的主要接口是IDataSet,可以将数据库模式的全部内容表示为单个IDataSet 实例。这些表本身由Itable 实例来表示。


IDataSet 的实现有很多,每一个都对应一个不同的数据源或加载机制。最常用的几种 IDataSet 实现为: 

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


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


1、添加maven依赖

		<dependency>
		  <groupId>org.dbunit</groupId>
		  <artifactId>dbunit</artifactId>
		  <version>2.5.4</version>
		  <scope>test</scope>
		</dependency>


2、创建测试类

通常一个测试类对应程序中的一个类,这些测试类为了重用一些公用的方法,可以创建一个基类,如下:

BaseDaoTest.java

package com.ConfigServer;

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.oracle.OracleDataTypeFactory;
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.junit4.SpringRunner;
@RunWith(SpringRunner.class)  
public abstract class BaseDaoTest {
	@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 oracle
        DatabaseConfig dbConfig = conn.getConfig();
        dbConfig.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY,  new OracleDataTypeFactory());
    }

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

    /**
     * 从xml文件中获取数据集,该文件保存的是用于断言的期望数据
     * 下一步可以通过ITable dbTable = xmlDataSet.getTable("表名")获取表结构和表数据,
     * FlatXmlDataSet用XML文件中该表的第一行数据来制定表的结构,所以,如果表的第一行
     * 有字段为空,则得到的表结构会与数据库表结构不一致,可能是字段顺序不同,也可以能
     * 是字段个数不同,因为xml文件中该表的后续的行会补充字段,如xml内容如下:
     * <dataset> 
     * <USER_INFO ID="1257436A2B3F4D31B4DF657043C30175" AGE="23" PHONE="12345678921"/>
     * <USER_INFO ID="AB2470AACFCA4E0BB6AF03B713DFDB99" NAME="休息休息" AGE="24"/>
     * </dataset>
     * 则得到的表结构为ID,AGE,PHONE,NAME
     * 而实际上数据库的表结构为ID,NAME,AGE,PHONE,可能还有更多字段,因为这些字段值都为空
     * 从而导致断言失败,因为表结构不一致,解决方法:
     * 首先修改xml文件,把表的第一行字段为空的用"[null]"占位符补上:
     * <USER_INFO ID="1257436A2B3F4D31B4DF657043C30175" NAME="[null]" AGE="23" ADDR="[null]" PHONE="12345678921"/>
     * 然后把ITable xmlTable = xmlDataSet.getTable(TABLE_USER_INFO);改成如下方式:
     * ReplacementDataSet replacementDataSet = createReplacementDataSet(xmlDataSet);
     * ITable xmlTable = replacementDataSet.getTable(TABLE_USER_INFO);
     */
    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)));
    }

    /**
     * 从数据库中获取数据集
     * 下一步可以通过ITable dbTable = dbDataSet.getTable("表名")获取表结构和表数据,
     * 这里直接从数据库获取表结构,所以,不管字段是否为空,表结构都与数据库一致
     */
    protected IDataSet getDBDataSet() throws SQLException {
        return conn.createDataSet();
    }

    /**
     * 从数据库获取查询数据集
     * 通过自己的query语句到数据库查询结果,代码如下:
     * QueryDataSet queryDataSet = getQueryDataSet();
     * queryDataSet.addTable("tableName or other String", "select * from yourTableName");
     * ITable dbTable = queryDataSet.getTable("tableName or other String");
     * 根据需要,该结果集可以作为期望值,也可以作为实际值
     * 作为期望值:比如,要测试一个查询方法,则可以把该结果与被测试的查询方法返回的结果做比较
     * 作为实际值:拿该结果与xml文件设置的期望值对比
     */
    protected QueryDataSet getQueryDataSet() throws SQLException {
        return new QueryDataSet(conn);
    }

    /**
     * 从Excel获取数据集
     * @param name Excel文件名
     */
    protected XlsDataSet getXlsDataSet(String name) throws SQLException, DataSetException,
            IOException {
        InputStream is = new FileInputStream(new File(ROOT_URL + name));
        return new XlsDataSet(is);
    }

    /**
     * 备份整个数据库
     */
    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");
    }

    /**
     * 备份指定的表
     */
    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");
    }

    /**
     * 回滚数据库
     * CLEAN_INSERT:DELETE_ALL和INSERT的组合,即先删除数据库的全部记录,然后再通过备份文件恢复;
     * UPDATE:用备份文件还原数据库中被修改的记录,前提是数据库表的数据要存在,如果缺少数据则会报错,
     * 如果有新增的数据,该数据不会被删掉;
     * 所以,用CLEAN_INSERT不管什么情况都可以回滚数据库,但如果有外键依赖会怎样?
     */
    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);
    }


    /**
     * 清空指定表的数据,很少使用
     */
    protected void clearTable(String tableName) throws Exception {
        DefaultDataSet dataset = new DefaultDataSet();
        dataset.addTable(new DefaultTable(tableName));
        DatabaseOperation.DELETE_ALL.execute(conn, dataset);
    }

    /**
     * 验证指定的表是否为空
     */
    protected void verifyTableEmpty(String tableName) throws DataSetException, SQLException {
        Assert.assertEquals(0, conn.createDataSet().getTable(tableName).getRowCount());
    }

    /**
     * 验证指定的表是否非空
     */
    protected void verifyTableNotEmpty(String tableName) throws DataSetException, SQLException {
        Assert.assertNotEquals(0, conn.createDataSet().getTable(tableName).getRowCount());
    }

    /**
     * 如果xml文件中的数据集存在列的值为空,可以用一个"[null]"占位符代替,然后调用该方法把"[null]"替换成null
     */
    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;
    }
    
}

创建测试类

DBUnitTest.java

package com.ConfigServer;

import org.dbunit.Assertion;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.ReplacementDataSet;
import org.dbunit.dataset.filter.DefaultColumnFilter;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.ConfigServer.service.DBUnitTestService;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)  
public class DBUnitTest extends BaseDaoTest {
	@Autowired
	private DBUnitTestService dbUnitTestService;
	
	/**
	 * 用户信息表表名
	 */
	private static final String TABLE_USER_INFO = "USER_INFO";
	
	@Test
	public void updateUserInfo() throws Exception {
		try {
			//backup table from DB
			backupCustom(TABLE_USER_INFO);
			//被测试的方法
			dbUnitTestService.updateUserInfo("AB2470AACFCA4E0BB6AF03B713DFDB99", "休息休息");
			
	        //get actual tableInfo from DB
	        IDataSet dbDataSet = getDBDataSet();
	        ITable dbTable = dbDataSet.getTable(TABLE_USER_INFO);
	        
	        //get expect Information from xml file
	        IDataSet xmlDataSet = getXmlDataSet("expect_user_info.xml");
	        ReplacementDataSet replacementDataSet = createReplacementDataSet(xmlDataSet);
	        ITable xmlTable = replacementDataSet.getTable(TABLE_USER_INFO);
	        
	        //exclude some columns which don't want to compare result
	        dbTable = DefaultColumnFilter.excludedColumnsTable(dbTable, new String[]{"ID"});
	        xmlTable = DefaultColumnFilter.excludedColumnsTable(xmlTable, new String[]{"ID"});
	        
//	        Assert.assertEquals(dbTable.getRowCount(), xmlTable.getRowCount());//比较行数
	        Assertion.assertEquals(xmlTable, dbTable);
		} catch (Exception e) {
		}finally{
			rollback();
		}
        
	}

}

被测试的类DBUnitTestService.java:

@Service
public class DBUnitTestService {
	@Autowired
	private JdbcTemplate jdbcTemplate;
	
	public void updateUserInfo(String id,String name) {
		String sql = "update USER_INFO t set t.NAME=? where t.ID=?";		
		jdbcTemplate.update(sql, name,id);
	}
}

用做期望值的xml文件expect_user_info.xml

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
  <USER_INFO ID="1257436A2B3F4D31B4DF657043C30175" NAME="[null]" AGE="23" ADDR="[null]" PHONE="12345678921"/>
  <USER_INFO ID="AB2470AACFCA4E0BB6AF03B713DFDB99" NAME="休息休息" AGE="24"/>
</dataset>


数据库内容如下:



3、其它知识点

1)存在外键依赖的情况

如果操作的数据库表被其它表依赖,则备份的时候要先备份父表再备份子表(即使子表没有被修改),否则,回滚会报错(因为回滚是先清空表再通过备份文件写入),如学生表"STUDENT_INFO"依赖于用户表"USER_INFO",则备份顺序为:

backupCustom("USER_INFO","STUDENT_INFO");




// TODO 继续补充更多内容













  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值