DBUnit的使用
Dbunit
【概念】
dbunit是一个基于junit扩展的数据库测试框架。它提供了大量的类对与数据库相关的操作进行了抽象和封装,虽然在80%的情况,你只需使用它极少的api。它通过使用用户自定义的数据集以及相关操作使数据库处于一种可知的状态,从而使得测试自动化、可重复和相对独立。虽然不用dbunit也可以达到这种目的,但是我们必须为此付出代价(编写大量代码,测试及维护),既然有了这么优秀的开源框架,我们又何必再造轮子。
DbUnit是为数据库驱动的项目提供的一个对JUnit 的扩展,除了提供一些常用功能,它可以将你的数据库置于一个测试轮回之间的状态。
【简介】
为依赖于其他外部系统(如数据库或其他接口)的代码编写单元测试是一件很困难的工作。在这种情况下,有效的单元必须隔离测试对象和外部依赖,以便管理测试对象的状态和行为。
使用mock object对象,是隔离外部依赖的一个有效方法。如果我们的测试对象是依赖于DAO的代码,mock object技术很方便。但如果测试对象变成了DAO本身,又如何进行单元测试呢?
开源的DbUnit项目,为以上的问题提供了一个相当优雅的解决方案。使用DbUnit,开发人员可以控制测试数据库的状态。进行一个DAO单元测试之前,DbUnit为数据库准备好初始化数据;而在测试结束时,DbUnit会把数据库状态恢复到测试前的状态。
【原理】
dbunit的与单元测试相关的两个最重要的核心是org.dbunit.database.IDatabaseConnection 和 org.dbunit.dataset.IDataSet ,前者是产品代码使用的数据库连接的一个简单的封装,后者是对单元测试人员自定义的数据集(通常以xml文件的形式存在,且xml文件的格式也有好几种)的封装。它会把数据库表里的数据和一个xml文件里表示的数据关联起来。也就是说 数据库表里的数据可以导出到一个对应的xml里,同时也可以将一个xml里的数据导入到数据库表里。是相互转换的。
数据库表里的数据<===>xml文件
可以做个测试如下:
package com.shunwang.icafe.contentEdit.bo;
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
imp
public class PageHomeBoTest extends TestBase {
/**
* 初始化
*/
public PageHomeBoTest() {
//预期文件路径
this.exceptFile="src/test/resources/config.page_home2.xml";
//生成的文件路径
this.inputFile="src/test/resources/config.page_home.xml";
//初始化数据源
this.dsBeanId="dataSource";
//初始化Spring配置文件
this.contextFile = "/applicationContext.xml";
}
public void AllowModuleTest() {
try {
// 两种备份数据的方法 建议使用第一种
// XmlDataSet.write(buildBackUpDataSet(), new FileOutputStream(new File(this.exceptFile)));
// FlatXmlDataSet.write(buildBackUpDataSet(), new FileOutputStream(new File(this.exceptFile)));
/**
* 你的操作写在这里
*/
//测试修改操作 【批准 修改了 相应的 状态】
Integer moduleKey = new Integer(1);
PageHomeBo pageHomeBo = (PageHomeBoImp) getBean("pageHomeBo");
pageHomeBo.allowModule(moduleKey, "测试用户");
/**
* 输出预期数据
*/
XmlDataSet.write(buildBackUpDataSet(), new FileOutputStream(new File(exceptFile)));
/**
* 还原数据 将config.page_home.xml中的数据还原到数据库
* 系统的缓存数据存在于 C:\Documents and Settings\Administrator\Local Settings\Temp
*/
//修改还原
UpdateOperation.CLEAN_INSERT.execute(getConnection(),new FlatXmlDataSet(this.backUpFile));
//插入还原 事实证明 两种还原基本上是相同的 采用 上面的UpdateOperation InsertIdentityOperation InsertOperation DeleteOperation基本一样的
// InsertIdentityOperation.CLEAN_INSERT.execute(getConnection(),new FlatXmlDataSet( this.backUpFile));
/**
* DbUnit 可以有不同的数据库操作,最主要的两种:
*DELETE_ALL ,它删除表中所有行。
*CLEAN_INSERT ,它删除表中所有行并插入数据集提供的行。
*
*/
/**
DatabaseOperation
DatabaseOperation.UPDATE:这个操作将从测试数据源中读取的数据集的内容更新到数据库中,注意这个操作正确执行的前提是测试数据表已经存在,如果不存在这个测试用例将会失败
DatabaseOperation.INSERT:这个操作把从测试数据源中读取的数据集的内容插入到数据库中,注意这个操作正确执行的前提是测试数据表不存在,这个操作将新建数据表。如果测试数据表已经存在这个测试用例将会失败。另外,在执行这个操作的时候要特别注意数据集中数据表的顺序,否则可能会因为违反外键约束而造成测试用例失败
DatabaseOperation.DELETE:这个操作会从数据库中删除数据,注意,这个操作只删除数据集中存在的数据行而不是整个数据表中的数据
DatabaseOperation.DELETE_ALL:这个操作删除数据表中的所有行,注意,这个操作也只影响数据集中声明了的数据表,数据集中没有涉及到的数据表中的数据不会删除
DatabaseOperation.TRUNCATE:这个操作将删除数据集中声明的数据表,如果数据中有些表并没有在预定义的数据集中提到,这个数据表将不会被影响。注意,这个操作是按照相反的顺序执行的
DatabaseOperation.REFRESH:顾名思义,这个操作将把预定义数据集中的数据同步到数据库中,也就是说这个操作将更新数据数中已有的数据、插入数据库中没有的数据。数据库中已有的、但是数据集中没有的行将不会被影响。我们用一个产品数据库的拷贝进行测试的时候可以使用这个操作将预定义数据同步到产品数据库中
DatabaseOperation.CLEAN_INSERT:删除所有的数据表中的数据,然后插入数据集中定义的数据,如果你想保证数据库是受控的,这个你会比较喜欢。
DatabaseOperation.NONE:不执行任何操作
CompositeOperation:将多个操作组合成一个,便以维护和重用
TransactionOperation:在一个事物内执行多个操作
IdentityInsertOperation:在使用MSSQL的时候,插入数据时IDENTITY列我们是没有办法控制的,用这个就可以控制了,只有在使用MSSQL的时候才会用得到
*/
/**
* 比较共有4种方法
* 1.直接比较结果集
* 2.通过excludeColumn方法从结果集抽出若干个字段形成一个临时表进行比较
* 3.在结果集中形成一个ITable的表对象进行比较
* 4.在ITable抽出若干字段形成零时表进行比较
* 建议使用第一种
*/
/**
* 第一种方法
*/
//从预期文件中读取预期结果集
IDataSet dataSet=new XmlDataSet(new FileInputStream(exceptFile));
//比较结果集
Assertion.assertEquals(buildBackUpDataSet(),dataSet);
/**
* 第二种方法
*/
//includeColumn是指 比较包含 后面String数组列 进行比较
//excludeColumn是指 比较排除 后面String数组列 进行比较
Assertion.assertEquals(this.includeColumn(this.buildExceptTableByName("config.page_home"), new String[]{"user_check"}),
this.includeColumn(this.buildTableByName("config.page_home"), new String[]{"user_check"}));
/**
* 第三种方法
*/
// //比较表数据 (以表的形式进行数据比较)
// //从xml的结果集中获取需要的表数据
// ITable iTable=dataSet.getTable("config.page_home");
// //第一种方法 获取数据库中的表数据
// //ITable dbTable =this.buildTableByName("config.page_home");
// //第二种方法 从结果集中获取所需要的表信息数据
// ITable dbTable=buildBackUpDataSet().getTable("config.page_home");
// //进行比较
// Assertion.assertEquals(dbTable,iTable);
/**
* 第四种方案
*/
// //通过表比较列
// System.out.println("===============数据列比较=================");
// //通过表获取到表中所需要的列 并将用户所需要的列形成一个新的临时表
// iTable=DefaultColumnFilter.excludedColumnsTable(iTable,new String[]{"user_check"});
// dbTable=DefaultColumnFilter.includedColumnsTable(dbTable,new String[]{"user_check"});
// Assertion.assertEquals(dbTable,iTable);
// //通过结果集比较列
} catch (Exception e) {
e.printStackTrace();
}catch (Error e) {
e.printStackTrace();
}
}
/**
* 生成数据集
*/
protected IDataSet buildBackUpDataSet(){
try {
QueryDataSet dataSet = new QueryDataSet(this.getConnection());
//从数据库导出结果集
dataSet.addTable("config.page_home");
//从数据库导出符合条件的结果集
//临时文件存储在 C:\Documents and Settings\Administrator\Local Settings\Temp
/**
* 生成含有条件的结果集
*/
// dataSet.addTable("config.page_home","select * from config.page_home where module_key < 2");
return dataSet;
} catch (AmbiguousTableNameException e) {
e.printStackTrace();
return null;
}
}
}
使用DBunit几点注意
1. 若果在导出数据时采用了选择性导出
如
dataSet.addTable("config.page_home","select * from config.page_home where module_key < 2");
xml之写入了 含有条件的 数据
则还原 如果使用
UpdateOperation.CLEAN_INSERT.execute(getConnection(),new FlatXmlDataSet( this.backUpFile));
或者
InsertIdentityOperation.CLEAN_INSERT.execute(getConnection(),new FlatXmlDataSet( this.backUpFile));
就会删掉原有的数据将xml的数据写入数据库
造成其他数据的丢失
建议:最好不要使用选择性导出数据,如果一定要使用,还原时请采用 UPDATE 进行还原如果采用FlatXmlDataSet备份数据时,当数据库中某条记录出现 Null 时,生成的文件不包含为null的字段