DbUnit是测试数据库的利器,不过要想弄明白还是需要一番研究。好在它的源代码不多,文档也还算全。我就在此做一个总结吧。
DbUnit.NET是DbUnit的.NET版,不过只推出了alpha版,而且自从06年以后就不再更新了。Stack Overflow上有一个帖子,提出了一些替代方案。
现在的DbUnit要求在测试时继承DBTestCase
,而不是之前的DatabaseTestCase
(前者继承自后者,而后者继承了junit的TestCase
)。DatabaseTestCase
包含两个抽象方法,getConnection()
和getDataSet()
,前者用来获取数据库连接,后者获取要测试的数据集。
DbUnit可以把所有表的记录存在一个数据集中:既可以是数据库中的表,也可以是文件中的数据。我们在此用FlatXmlDataSet
来演示。
顺便提一句,DbUnit中还存在另一种格式的数据集XmlDataSet
,它们的区别如下:
在FaltXmlDataSet
对应的XML文件里,元素名称对应数据库表名,元素的属性(attribute)对应表的列。如:
<dataset><PersonName="Kirin"Age="31"Location="Beijing"/><PersonName="Jade"Age="30"/></dataset>
要注意,如果数据库中某一条字段为null,在flat XML中将不会显示该attribute。另外,FlatXmlDataSet
用XML文件中该表的第一行数据来制定表的结构。因此,如果数据库中某个字段所有记录都为null,或者恰巧第一条记录为null,那么得到的表结构与原数据库的表结构就不一致了,测试就会失败。FlatXmlDataSet中存在一个column sensing的概念,在从文件加载数据时,将该属性设置为true,就会根据第一行展现出来的表结构,自动将别的行的列补齐。
在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用空元素来表示。
我们可以手写XML来准备数据,也可以从数据库中导出现有的数据,用FlatXmlDataSet.write()
静态方法即可,例如:
QueryDataSet dataSet =newQueryDataSet(connection); dataSet.addTable(TABLE_NAME,"select * from "+ TABLE_NAME); dataSet.addTable(...);FlatXmlDataSet.write(dataSet,newFileOutputStream("data.xml"));
有了文件数据,我们就需要重写getDataSet()
,让它加载文件中的数据并返回。
@OverrideprotectedIDataSet getDataSet()throwsException{// set column sensing as true, so it can dynamically and columns with null value. returnnewFlatXmlDataSetBuilder().setColumnSensing(true).build(newFileInputStream("data.xml"));}
DBTestCase
重写了getConnection()
,并把它设置为final,将获取connection的操作委托给IDatabaseTester
,我们可以通过重写getDatabaseTester()
方法来设置具体的IDatabaseTester
。Dbunit中,IDatabaseTester
的实现类一共有四个:
它们的用途不言自明。
DatabaseTestCase
重写了TestCase
里的setUp()
和tearDown()
方法。
protectedvoid setUp()throwsException{super.setUp();finalIDatabaseTester databaseTester = getDatabaseTester(); assertNotNull("DatabaseTester is not set", databaseTester ); databaseTester.setSetUpOperation( getSetUpOperation()); databaseTester.setDataSet( getDataSet()); databaseTester.setOperationListener(getOperationListener()); databaseTester.onSetup();}protectedvoid tearDown()throwsException{try{finalIDatabaseTester databaseTester = getDatabaseTester(); assertNotNull("DatabaseTester is not set", databaseTester ); databaseTester.setTearDownOperation( getTearDownOperation()); databaseTester.setDataSet( getDataSet()); databaseTester.setOperationListener(getOperationListener()); databaseTester.onTearDown();}finally{ tester =null;super.tearDown();}}
可以看出它们的大体意图:为tester设置操作、数据集和监听器,然后执行相应的操作。获取数据集的是抽象方法,需要我们来实现。监听器主要负责在得到数据连接或setUp、tearDown结束后执行的操作,使用默认实现即可。我们主要来说说getSetUpOperation
和getTearDownOperation
返回的DatabaseOperation
。
DatabaseOperation
定义了对数据库进行的操作,它是一个抽象类,通过静态字段提供了几种内置的实现:
getTearDownOperation
的默认返回值。getSetUpOeration
的默认返回值。由此我们可以总结出,在一个测试执行前后,DbUnit会为我们做哪些工作:
我们可以根据需要,在测试类中重写setUp
和tearDown
,以实现定制的需求。比如,数据库中已经有一些数据,我们不希望数据集中的数据对它们产生任何影响,这时可以先将数据库中的数据备份到内存中,等测试完成后再恢复到数据库中,代码如下:
privateIDataSet dataSetBackup;privatestaticfinalString[] TABLE_NAMES =newString[]{"..."};@Overrideprotectedvoid setUp()throwsException{ dataSetBackup =newCachedDataSet(getConnection().createDataSet(TABLE_NAMES));super.setUp();}@Overrideprotectedvoid tearDown()throwsException{try{finalIDatabaseTester databaseTester = getDatabaseTester(); assertNotNull("DatabaseTester is not set", databaseTester ); databaseTester.setTearDownOperation( getTearDownOperation()); databaseTester.setDataSet( dataSetBackup );// 这里不使用getDataSet(),而是使用备份的数据库中数据 databaseTester.setOperationListener(getOperationListener()); databaseTester.onTearDown();}finally{ tester =null; dataSetBackup =null;//super.tearDown(); // 这里不再调用基类的tearDown}}@OverrideprotectedDatabaseOperation getTearDownOperation(){returnDatabaseOperation.CLEAN_INSERT;}
测试前用CLEAN_INSERT,是用数据集覆盖数据库,测试后用CLEAN_INSERT,使用备份的数据库覆盖之前插入到数据库中的数据集。
完整的基类代码在这里。
好了,现在可以开始测试了。