DbUnit入门实战

相信做过单元测试的人都会对JUnit非常的熟悉了,今天要介绍的DbUnit(http://dbunit.sourceforge.net/)则是专门针对数据库测试的对JUnit的一个扩展,它可以将测试对象数据库置于一个测试轮回之间的状态。鉴于目前国内介绍DbUnit的系统教程比较少见,本文将分从理论和实例两个方面带你领略DbUnit的精彩世界。

DbUnit设计理念熟悉单元测试的开发人员都知道,在对数据库进行单元测试时候,通常采用的方案有运用模拟对象(mockobjects)和stubs两种。通过隔离关联的数据库访问类,比如JDBC的相关操作类,来达到对数据库操作的模拟测试。然而某些特殊的系统,比如利用了EJB的CMP(container-managed persistence)的系统,数据库的访问对象是在最底层而且很隐蔽的,那么这两种解决方案对这些系统就显得力不从心了。

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)性别

birthday date 出生日期

--------------------------------------------------------------------------------准备数据如下:

--------------------------------------------------------------------------------

idnamesexbirthday

0001翁仔m1979-12-31

0002王翠花f1982-08-09

--------------------------------------------------------------------------------

测试对象类为StudentOpe.java,里边有2个方法:

findStudent(Stringid):根据主键id找记录

addStudent(Student student) :添加一条记录

在测试addStudent方法时候,我们准备添加如下一条数据

--------------------------------------------------------------------------------

idnamesexbirthday

0088 王耳朵 m 1982-01-01

--------------------------------------------------------------------------------那么在执行该方法后,数据库的student表里的数据是这样的:

--------------------------------------------------------------------------------

idnamesexbirthday

0001翁仔m1979-12-31

0002王翠花f1982-08-09

0088 王耳朵 m 1982-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 =new DatabaseConnection(conn);

//对数据库中的操作对象表student进行备份

QueryDataSetbackupDataSet=newQueryDataSet(connection);

backupDataSet.addTable("student");

file=File.createTempFile("student_back",".xml");//备份文件

FlatXmlDataSet.write(backupDataSet,new FileOutputStream(file));

//准备数据的读入

IDataSetdataSet=newFlatXmlDataSet(newFileInputStream("student_pre.xml"));

DatabaseOperation.CLEAN_INSERT.execute(connection,dataSet);

}catch(Exception e){

e.printStackTrace();

}finally{

try{

if(connection!=null)connection.close();

}catch(SQLExceptione){}

}

}

--------------------------------------------------------------------------------3 实装测试方法,详细见代码注释。

*检索类方法,可以利用assertEquals()方法,拿表的字段进行比较。

--------------------------------------------------------------------------------

//findStudent

publicvoidtestFindStudent()throwsException{

//执行findStudent方法

StudentOpestudentOpe=newStudentOpe();

Student result = 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");

//执行追加方法

Student result = studentOpe.addStudent(newStudent);

//预想结果和实际结果的比较IDatabaseConnection connection=null;

try{

//预期结果取得

IDataSetexpectedDataSet=newFlatXmlDataSet(newFileInputStream("student_exp.xml"));

ITable expectedTable = expectedDataSet.getTable("student");

//实际结果取得

Connectionconn=getConnection();

connection =new DatabaseConnection(conn);

IDataSet databaseDataSet = connection.createDataSet();ITable actualTable = 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()方法里边,把数据库还原到测试前状态

--------------------------------------------------------------------------------protected void tearDown() throws Exception{

IDatabaseConnection connection =null;

try{

super.tearDown();

Connectionconn=getConnection();

connection =new DatabaseConnection(conn);

IDataSet dataSet = new FlatXmlDataSet(file);DatabaseOperation.CLEAN_INSERT.execute(connection,dataSet);

}catch(Exception e){

e.printStackTrace();

}finally{

try{

if(connection!=null)connection.close();

}catch(SQLExceptione){}

}

}

 

 

曾经一直把Dbunit当做是测试数据库的东西(其实本来也就是),最近在研究Appfuse是时候发现Dbunit对数据库的数据进行load和export非常方便,尤其是在自动填充数据库,或者导出数据的时候(两个可以反向进行了),或者是WebTest测试的时候,尤为重要了,简单的几句话,就能完成数据的装载,导出,或者是查询了,下面有个例子可以说明这情况.不过注意的一点就是,配置路径了.在Eclipse中在设置Ant的ClassPath时候就要把Dbunit和数据库的驱动程序Jar包加进去,然后别的都通过下面的例子就OK了
 
<project name="SimpleTest" basedir="." default="load">
 <property name="dbDriver" value="oracle.jdbc.driver.OracleDriver" />

<propertyname="dbUrl"value="jdbc:oracle:thin:@192.168.104.47:1521:esample"/>

<propertyname="dbUser"value="esample"/>

 <property name="dbPassword" value="esample" />
 <taskdef name="dbunit" classname="org.dbunit.ant.DbUnitTask"  />
 <target name="load" description="Loads the database with sample data">

<propertyname="operation"value="CLEAN_INSERT"/>

<propertyname="file"value="partial.xml"/>

<dbunitdriver="${dbDriver}"url="${dbUrl}"

userid="${dbUser}"password="${dbPassword}">

<operationtype="${operation}"src="${file}"format="xml"/>

</dbunit>

 </target>
 <target name="export">

<dbunitdriver="${dbDriver}"url="${dbUrl}"userid="${dbUser}"password="${dbPassword}">

<exportdest="partial.xml"format="xml">

<queryname="QueryExhibtion"sql="SELECTExhibition_IdFROMEx_exhibition"/>

<tablename="ex_exhibition"/>

</export>

</dbunit>

</target>

</project>
  要先执行export,这样就会自动生成一个数据导出文件,如果有的话就会覆盖,然后在用load方法就可以让数据库加载刚才生成的那些数据了,具体的加载方式要设置dbunit中的operation属性了,有UPDATE, INSERT, DELETE, DELETE_ALL,REFRESH, CLEAN_INSERT, MSSQL_INSERT, MSSQL_REFRESH,MSSQL_CLEAN_INSERT等参数了
 这样的话项目在持续集成的时候就方便多了,关于数据库的东西都是有Dbunit自动生成了,也算是Xp方法的一个数据库的实践把.注dbunit的地址是:http://www.dbunit.org/,上面的例子是在DBunit2.1中测试通过.

相关推荐