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 表示
前提条件
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 这时候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()); 这个例子的期望结果是定义在excel里的,目前只支持xls文件,即Excel97-2003 这里用到了includedColumnsTable,只比较excel里定义的那些字段。 ##转载注明出处: http://www.cnblogs.com/wade-xu/p/4547381.html
Example 4QueryDataSet @Test public void testQueryBundles() throws Exception { backupCustom(TABLE_FILE_VERSION, TABLE_DOCUMENTS_MASTER); bundleDao.insertBundle(getBundles()); List<InfoEntity> list = bundleDao.queryBundles(); //get expect result from DB QueryDataSet queryDataSet = getQueryDataSet(); queryDataSet.addTable("test", "select * from Documents_Master"); ITable dbTable = queryDataSet.getTable("test"); Assert.assertEquals(dbTable.getRowCount(), list.size()); rollback(); } 通过自己的query语句查到的结果作为期望结果与调用Dao层取得的实际结果比较断言。
Example 5@Test public void testDeleteAll() throws Exception { backupCustom(TABLE_FILE_VERSION, TABLE_DOCUMENTS_MASTER); bundleDao.insertBundle(getBundles()); verifyTableNotEmpty(TABLE_DOCUMENTS_MASTER); bundleDao.deleteAll(); verifyTableEmpty(TABLE_DOCUMENTS_MASTER); rollback(); }
Run as JUnit 测试结果如下图,毕竟是实际读写数据库,速度还是比较慢的, 49秒多。(慢跟我的本地环境连远程数据库也有很大关系) ##转载注明出处: http://www.cnblogs.com/wade-xu/p/4547381.html
Troubleshooting1. java.lang.NoSuchMethodError: org.apache.poi.hssf.usermodel.HSSFDateUtil.isCellDateFormatted(Lorg/apache/poi/hssf/usermodel/HSSFCell; --用最新的包2.5.1可以解决这个问题
2. 控制台报警 WARN org.dbunit.dataset.AbstractTableMetaData - Potential problem found: The configured data type factory 'class org.dbunit.dataset.datatype.DefaultDataTypeFactory' might cause problems with the current database 'MySQL' (e.g. some datatypes may not be supported properly). --需要配置如下属性: DatabaseConfig dbConfig = conn.getConfig();
3. 如遇到这个错误 Extra columns on line x. Those columns will be ignored. Please add the extra columns to line 1, or use a DTD to make sure the value of those columns are populated. 则需要用setColumnSensing FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();
4. 有关联的表 需要一起backup 5. 基类BaseDaoTest 因为没有@Test测试方法,所以需要写成抽象类,不然会出现 java.lang.Exception: No runnable methods 6. 如果想让DBUnit支持Excel2007 xlsx格式的文件的话,需要自己下载源码,把org.apache.poi.hssf 改成 xssf, 或者ss支持新老格式,重新编译, 再依赖进来。
参考官方文档: http://dbunit./
感谢阅读,如果您觉得本文的内容对您的学习有所帮助,您可以点击右下方的推荐按钮,您的鼓励是我创作的动力。 ##转载注明出处: http://www.cnblogs.com/wade-xu/p/4547381.html
|
|