使用DbUnit做高效的单元测试,dbunit单元测试,DbUnit介绍做单元测
使用DbUnit做高效的单元测试,dbunit单元测试,DbUnit介绍做单元测
DbUnit介绍
做单元测试时,如果测试的对象依赖于数据库或者其他组件,测试的时候就会很费劲,需要mock很多方法。Manuel Laflamme做的开源项目DbUnit框架为依赖于数据库对象的单元测试提供了一种优雅的方案。 使用DbUnit开发人员可以控制数据库的状态,也不需要在每次测试时手动初始化数据。
使用DbUnit的准备工作
第一步我们需要配置DbUnit相关的数据库的结构定义文件,这些文件是xml格式的,例如,我们的数据库里有一个EMPLOYEE表,其结构如下:
这个表里有一条数据,内容如下:
那么DbUnit需要的数据库表结构定义文件如下
<EMPLOYEE employee_uid='1' start_date='2001-11-01' first_name='Andrew' ssn='xxx-xx-xxxx' last_name='Glover' />
上面的xml就是程序中使用的数据库EMPLOYEE表的种子文件。
在实际程序中我们需要创建一系列的表来做为种子文件,如下示例:
<?xml version='1.0' encoding='UTF-8'?><dataset> <EMPLOYEE employee_uid='1' start_date='2001-01-01' first_name='Drew' ssn='000-29-2030' last_name='Smith' /> <EMPLOYEE employee_uid='2' start_date='2002-04-04' first_name='Nick' ssn='000-90-0000' last_name='Marquiss' /> <EMPLOYEE employee_uid='3' start_date='2003-06-03' first_name='Jose' ssn='000-67-0000' last_name='Whitson' /></dataset>
准备工作就是这样了,下面我们看下如下使用DbUnit
使用DbUnit
DbUnit框架提供了从JUnit的TestCase类继承的DatabaseTestCase类作为测试类的基类。DatabaseTestCase是一个抽象类,它定义了getConnection()
和getDataSet()
两个方法,所有子类必须自己实现这两个方法。
getConnection()
方法要返回IDatabaseConnection
对象,该对象是对JDBC Connection的封装。如下代码示例:
protected IDatabaseConnection getConnection() throws Exception { Class driverClass = Class.forName("org.gjt.mm.mysql.Driver"); Connection jdbcConnection = DriverManager.getConnection( "jdbc:mysql://127.0.0.1/hr", "hr", "hr"); return new DatabaseConnection(jdbcConnection);}
getDataSet()
方法期望返回IDataSet对象,本质上就是返回前面定义的种子文件中的xml数据,如下示例:
protected IDataSet getDataSet() throws Exception { return new FlatXmlDataSet( new FileInputStream("hr-seed.xml"));}
有了这两个方法的,DbUnit就可以运行默认的操作了;另外DatabaseTestCase类还提供了getSetUpOperation()
和getTearDownOperation()
两个方法,这两个方法可以控制数据的状态。
通常可以通过getSetUpOperation()
方法来刷新一下数据的状态,刷新操作可以使用种子文件来更新测试数据库的状态。 getTearDownOperation()
不做任何操作。
如下实现:
protected DatabaseOperation getSetUpOperation() throws Exception { return DatabaseOperation.REFRESH; }protected DatabaseOperation getTearDownOperation() throws Exception { return DatabaseOperation.NONE;}
另外一个非常常用的操作是在getSetUpOperation()
方法中执行CLEAN_INSERT,这样就可以删除在种子文件中的记录,然后重新插入记录。
代码示例:
假定我们要做一个人力资源的项目,我们需要为业务层操作Employee的类来写一系列的测试用例,包括创建,获得,编辑,删除Employee等方法,方法的接口定义如下:
public void createEmployee( EmployeeValueObject emplVo )public EmployeeValueObject getEmployeeBySocialSecNum( String ssn )public void updateEmployee( EmployeeValueObject emplVo )public void deleteEmployee( EmployeeValueObject emplVo )
下面的DbUnit种子文件,命名为employee-hr-seed.xml,会在后面用到
<?xml version='1.0' encoding='UTF-8'?><dataset> <EMPLOYEE employee_uid='1' start_date='2001-01-01' first_name='Drew' ssn='333-29-9999' last_name='Smith' /> <EMPLOYEE employee_uid='2' start_date='2002-04-04' first_name='Nick' ssn='222-90-1111' last_name='Marquiss' /> <EMPLOYEE employee_uid='3' start_date='2003-06-03' first_name='Jose' ssn='111-67-2222' last_name='Whitson' /></dataset>
我们的测试类为EmployeeSessionFacadeTest
, 从DatabaseTestCase
继承,并实现了getConnection()
和 getDataSet()
方法前者返回数据库连接,后者返回上面定义的种子文件中的DataSet。
如下测试的方法相当的简单,DbUnit帮我们处理了复杂的数据库数据操作的细节,要测试getEmployeeBySocialSecNum()
方法只需要简单的传一个种子文件中有的序列号即可,如:“333-29-9999”
public void testFindBySSN() throws Exception{ EmployeeFacade facade = //obtain somehow EmployeeValueObject vo = facade.getEmployeeBySocialSecNum("333-29-9999"); TestCase.assertNotNull("vo shouldn't be null", vo); TestCase.assertEquals("should be Drew", "Drew", vo.getFirstName()); TestCase.assertEquals("should be Smith", "Smith", vo.getLastName());}
要测试createEmployee也很简单,我们只需要执行创建操作,并通过getEmployeeBySocialSecNum方法判断插入的数据是否存在就可以了,如下是示例代码片段:
public void testEmployeeCreate() throws Exception{ EmployeeValueObject empVo = new EmployeeValueObject(); empVo.setFirstName("Noah"); empVo.setLastName("Awan"); empVo.setSSN("564-55-5555"); EmployeeFacade empFacade = //obtain from somewhere empFacade.createEmployee(empVo); //perform a find by ssn to ensure existence}
测试updateEmployee()方法需要四个步骤,首先要找到一个要更新的Employee实体,然后更新对象,然后在通过get方法获得Employee,然后看更新是否生效了。
public void testUpdateEmployee() throws Exception{ EmployeeFacade facade = //obtain facade EmployeeValueObject vo = facade.getEmployeeBySocialSecNum("111-67-2222"); TestCase.assertNotNull("vo was null", vo); TestCase.assertEquals("first name should be Jose", "Jose", vo.getFirstName()); vo.setFirstName("Ramon"); facade.updateEmployee(vo); EmployeeValueObject newVo = facade.getEmployeeBySocialSecNum("111-67-2222"); TestCase.assertNotNull("vo was null", newVo); TestCase.assertEquals("name should be Ramon", "Ramon", newVo.getFirstName());}
删除方法的测试和更新差不多,首先找到现在存在的Employee实体,然后执行删除,然后尝试获得这个实体,如果没找到就说明删除操作是没有问题的。
public void testDeleteEmployee() throws Exception{ EmployeeFacade facade = //obtain facade EmployeeValueObject vo = facade.getEmployeeBySocialSecNum("222-90-1111"); TestCase.assertNotNull("vo was null", vo); facade.deleteEmployee(vo); try{ EmployeeValueObject newVo = facade.getEmployeeBySocialSecNum("222-90-1111"); TestCase.fail("returned removed employee"); }catch(Exception e){ //ignore }}
就是这么简单,下面我们看下如何在Ant中使用DbUnit
DbUnit in Ant
在Ant中使用DbUnit不需要继承DatabaseTestCase类,需要一个ant task。 它允许在编辑文件时控制数据库连接,Ant的task非常的强大,通过如下声明来定义一个测试的task
<junit printsummary="yes" haltonfailure="yes"> <formatter type="xml"/> <batchtest fork="yes" todir="${reports.tests}"> <fileset dir="${src.tests}"> <include name="**/*Test.java"/> </fileset> </batchtest></junit>
在DbUnit task中,可以执行测试的数据库连接和需要的种子文件,在每个setup操作中dbunit都会使用种子文件来初始化数据库。
<taskdef name="dbunit" classname="org.dbunit.ant.DbUnitTask"/><dbunit driver=" org.gjt.mm.mysql.Driver " url=" jdbc:mysql://127.0.0.1/hr " userid="hr" password="hr"> <operation type="INSERT" src="seedFile.xml"/></dbunit>
可以定义tear down操作,来删除目标数据库中的种子数据,如下定义:
<dbunit driver=" org.gjt.mm.mysql.Driver " url=" jdbc:mysql://127.0.0.1/hr " userid="hr" password="hr"> <operation type="DELETE" src="seedFile.xml"/></dbunit>
这样Junit task就可以在开始时初始化测试数据库,在测试结束时删除载入的数据了。
<taskdef name="dbunit" classname="org.dbunit.ant.DbUnitTask"/><!-- set up operation --><dbunit driver=" org.gjt.mm.mysql.Driver " url=" jdbc:mysql://127.0.0.1/hr " userid="hr" password="hr"> <operation type="INSERT" src="seedFile.xml"/></dbunit><!-- run all tests in the source tree --><junit printsummary="yes" haltonfailure="yes"> <formatter type="xml"/> <batchtest fork="yes" todir="${reports.tests}"> <fileset dir="${src.tests}"> <include name="**/*Test*.java"/> </fileset> </batchtest></junit><!-- tear down operation --><dbunit driver=" org.gjt.mm.mysql.Driver " url=" jdbc:mysql://127.0.0.1/hr " userid="hr" password="hr"> <operation type="DELETE" src="seedFile.xml"/></dbunit>
本文翻译自: http://www.onjava.com/pub/a/onjava/2004/01/21/dbunit.html
用户点评