fairystepwgl 2011-04-28
相信做过单元测试的人都会对JUnit非常的熟悉了,今天要介绍的DbUnit(http://dbunit.sourceforge.net/)则是专门针对数据库测试的对JUnit的一个扩展,它可以将测试对象数据库置于一个测试轮回之间的状态。鉴于目前国内介绍DbUnit的系统教程比较少见,本文将分从理论和实例两个方面带你领略DbUnit的精彩世界。
DbUnit设计理念
熟悉单元测试的开发人员都知道,在对数据库进行单元测试时候,通常采用的方案有运用模拟对象(mockobjects)和stubs两种。通过隔离关联的数据库访问类,比如JDBC的相关操作类,来达到对数据库操作的模拟测试。然而某些特殊的系统,比如利用了EJB的CMP(container-managedpersistence)的系统,数据库的访问对象是在最底层而且很隐蔽的,那么这两种解决方案对这些系统就显得力不从心了。
DBUnit的设计理念就是在测试之前,备份数据库,然后给对象数据库植入我们需要的准备数据,最后,在测试完毕后,读入备份数据库,回溯到测试前的状态;
而且又因为DBUnit是对JUnit的一种扩展,开发人员可以通过创建测试用例代码,在这些测试用例的生命周期内来对数据库的操作结果进行比较。
DbUnit测试基本概念和流程
基于DbUnit的测试的主要接口是IDataSet。IDataSet代表一个或多个表的数据。
可以将数据库模式的全部内容表示为单个IDataSet实例。这些表本身由Itable实例来表示。
IDataSet的实现有很多,每一个都对应一个不同的数据源或加载机制。最常用的几种IDataSet实现为:
FlatXmlDataSet:数据的简单平面文件XML表示
QueryDataSet:用SQL查询获得的数据
DatabaseDataSet:数据库表本身内容的一种表示
XlsDataSet:数据的excel表示
一般而言,使用DbUnit进行单元测试的流程如下:
1根据业务,做好测试用的准备数据和预想结果数据,通常准备成xml格式文件。
2在setUp()方法里边备份数据库中的关联表。
3在setUp()方法里边读入准备数据。
4对测试类的对应测试方法进行实装:执行对象方法,把数据库的实际执行结果和预想结果进行比较。
5在tearDown()方法里边,把数据库还原到测试前状态。
DbUnit开发实例
下面通过一个实例来说明DbUnit的实际运用。
实例准备
比如有一个学生表[student],结构如下:
idchar(4)pk学号
namechar(50)姓名
sexchar(1)性别
birthdaydate出生日期
准备数据如下:
idnamesexbirthday
0001翁仔m1979-12-31
0002王翠花f1982-08-09测试对象类为StudentOpe.java,里边有2个方法:
findStudent(Stringid):根据主键id找记录
addStudent(Studentstudent):添加一条记录
在测试addStudent方法时候,我们准备添加如下一条数据
idnamesexbirthday
0088王耳朵m1982-01-01
那么在执行该方法后,数据库的student表里的数据是这样的:
idnamesexbirthday
0001翁仔m1979-12-31
0002王翠花f1982-08-09
0088王耳朵m1982-01-01
然后我们说明如何对这2个方法进行单元测试。
实例展开
1把准备数据和预想数据转换成xml文件
student_pre.xml
<?xmlversion='1.0'encoding="gb2312"?>
<dataset>
<studentid="0001"name="翁仔"sex="m"birthday="1979-12-31"/>
<studentid="0002"name="王翠花"sex="f"birthday="1982-08-09"/>
</dataset>
student_exp.xml
<?xmlversion='1.0'encoding="gb2312"?>
<dataset>
<studentid="0001"name="翁仔"sex="m"birthday="1979-12-31"/>
<studentid="0002"name="王翠花"sex="f"birthday="1982-08-09"/>
<studentid="0088"name="王耳朵"sex="m"birthday="1982-01-01"/>
</dataset>
2实装setUp方法,详细见代码注释。
protectedvoidsetUp(){
IDatabaseConnectionconnection=null;
try{
super.setUp();
//本例使用postgresql数据库
Class.forName("org.postgresql.Driver");
//连接DB
Connectionconn=DriverManager.getConnection("jdbc:postgresql:testdb.test","postgres","postgres");
//获得DB连接
connection=newDatabaseConnection(conn);
//对数据库中的操作对象表student进行备份
QueryDataSetbackupDataSet=newQueryDataSet(connection);
backupDataSet.addTable("student");
file=File.createTempFile("student_back",".xml");//备份文件
FlatXmlDataSet.write(backupDataSet,newFileOutputStream(file));
//准备数据的读入
IDataSetdataSet=newFlatXmlDataSet(newFileInputStream("student_pre.xml"));
DatabaseOperation.CLEAN_INSERT.execute(connection,dataSet);
}catch(Exceptione){
e.printStackTrace();
}finally{
try{
if(connection!=null)connection.close();
}catch(SQLExceptione){}
}
}
3实装测试方法,详细见代码注释。
*检索类方法,可以利用assertEquals()方法,拿表的字段进行比较。
//findStudent
publicvoidtestFindStudent()throwsException{
//执行findStudent方法
StudentOpestudentOpe=newStudentOpe();
Studentresult=studentOpe.findStudent("0001");
//预想结果和实际结果的比较
assertEquals("翁仔",result.getName());
assertEquals("m",result.getSex());
assertEquals("1979-12-31",result.getBirthDay());
}
*更新,添加,删除等方法,可以利用Assertion.assertEquals()方法,拿表的整体来比较。
publicvoidtestAddStudent()throwsException{
//执行addStudent方法
StudentOpestudentOpe=newStudentOpe();
//被追加的记录
StudentnewStudent=newStudent("0088","王耳朵","m","1982-01-01");
//执行追加方法
Studentresult=studentOpe.addStudent(newStudent);
//预想结果和实际结果的比较
IDatabaseConnectionconnection=null;
try{
//预期结果取得
IDataSetexpectedDataSet=newFlatXmlDataSet(newFileInputStream("student_exp.xml"));
ITableexpectedTable=expectedDataSet.getTable("student");
//实际结果取得
Connectionconn=getConnection();
connection=newDatabaseConnection(conn);
IDataSetdatabaseDataSet=connection.createDataSet();
ITableactualTable=databaseDataSet.getTable("student");
//比较
Assertion.assertEquals(expectedTable,actualTable);
}finally{
if(connection!=null)connection.close();
}
}
*如果在整体比较表的时候,有个别字段不需要比较,可以用DefaultColumnFilter.excludedColumnsTable()方法,
将指定字段给排除在比较范围之外。比如上例中不需要比较birthday这个字段的话,那么可以如下代码所示进行处理:
ITablefilteredExpectedTable=DefaultColumnFilter.excludedColumnsTable(expectedTable,newString[]{"birthday"});
ITablefilteredActualTable=DefaultColumnFilter.excludedColumnsTable(actualTable,newString[]{"birthday"});
Assertion.assertEquals(filteredExpectedTable,filteredActualTable);
4在tearDown()方法里边,把数据库还原到测试前状态
protectedvoidtearDown()throwsException{
IDatabaseConnectionconnection=null;
try{
super.tearDown();
Connectionconn=getConnection();
connection=newDatabaseConnection(conn);
IDataSetdataSet=newFlatXmlDataSet(file);
DatabaseOperation.CLEAN_INSERT.execute(connection,dataSet);
}catch(Exceptione){
e.printStackTrace();
}finally{
try{
if(connection!=null)connection.close();
}catch(SQLExceptione){}
}
}
最后
无疑,使用DbUnit能够极大的提高数据库测试的效率,希望通过本文能够让您掌握这一数据库测试的利器