目的:
初步学习DBUnit,掌握DBUnit基础用法,为数据层测试做准备。
这个一个从junit上扩展的单元测试框架,主要集中于数据层的测试。使用很简单(会使了以后),文档非常的少,尤其是中文文档,没有什么详细的解释,官方几页简单的介绍,如果不是动手去做一做,很难明白。
由于这个框架是基于jdbc数据测试的,现在的环境都是SSH的架构,看着那几页文档,不动手,真不明白它怎么实现的。
通过这个文档,希望能让像我这样习惯了解清楚再动手的人,简单的开始使用这个框架进行测试了。
开始吧:
看到这个文章的人,大概都了解一个基本的junit单元测试结构了,那么我们从一个基本测试结构开始:
这个结构只有一个测试类:
package com.ceopen.wjx.test.dbunit;
……代码略……
public class TestSql extends TestCase{
……代码略……
public void testSql() throws SQLException, Exception{
Class driverClass = Class.forName("com.mysql.jdbc.Driver");
Connection jdbcConnection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/ZSHOP-DEMO", "root", "root");
Statement statement = jdbcConnection.createStatement();
ResultSet rs = statement.executeQuery("SELECT count(*) as NUM FROM `DEMO`.`UC_ACCOUNT` ; ");
rs.next();
System.out.println(rs.getString("NUM"));
int num = rs.getInt("NUM");
assertTrue(num>0);
}
……代码略……
}
这里需要一个mySql的数据库和相关驱动jar包,或者你可以直接改为你需要的任何数据库连接。
现在它只是一个标准的junit测试类,没有任何稀奇的地方。你要是还没有使用过junit,先google一下,花一个小时补习一下吧。
这个类里面,连接了数据库,查询了一个叫做UC_ACCOUNT的表的记录数。我们假设这个表里面存在若干记录,基本的jdbc查询,没什么可说的,还是让我们将注意力集中在DBUnit上面吧。
DBUnit入场:
下面该是重头戏了,DBUnit,它到底能干什么呢?它主要的任务是在测试开始的时候搭建测试环境,先来使用一下看看。
首先将TestSql 的父类从TestCase改为DBTestCase。这是一个DBUnit多次继承TestCase后的实现。实现getDataSet()方法,然后在构造函数中声明几个环境变量。改完的代码如下:
public class TestSql extends DBTestCase {
// 构造函数
public TestSql() {
super(); System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, "com.mysql.jdbc.Driver" ); System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL, "jdbc:mysql://localhost:3306/ZSHOP-DEMO" );
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME, "root" );
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD, "root" );
}
// 测试方法
public void testSql() throws SQLException, Exception{
Class driverClass = Class.forName("com.mysql.jdbc.Driver");
Connection jdbcConnection = DriverManager.getConnection("jdbc:mysql://localhost:3306/DEMO", "root", "root");
Statement statement = jdbcConnection.createStatement();
ResultSet rs = statement.executeQuery("SELECT count(*) as NUM FROM `ZSHOP-DEMO`.`UC_ACCOUNT` LIMIT 0, 100; ");
rs.next();
System.out.println(rs.getString("NUM"));
int num = rs.getInt("NUM");
assertTrue(num>0);
}
@Override
protected IDataSet getDataSet() throws Exception {
return null;
}
}
下面是重点,初始化数据,我们要初始化UC_ACCOUNT这个表,如果里面的数据对你重要,你最好先备份一下数据库。
首先,建立一个新的xml文件,就叫做uc_account.xml吧,内容如下:
<?xml version='1.0' encoding="gb2312"?>
<dataset>
<UC_ACCOUNT ID="0001" VERSION="1" USER_LOGIN_NAME="test001" />
<UC_ACCOUNT ID="0002" VERSION="2" USER_LOGIN_NAME="test002" />
</dataset>
这里注意,作为根元素dataset是必须的,它的子元素名称是表名称,属性名称是字段名称,属性值就是字段值。字段值不要与现有数据库中的值重复,后面你会知道为什么的。
然后,将getDataSet中的方法补全,如下:
……代码略……
@Override
protected IDataSet getDataSet() throws Exception {
return new FlatXmlDataSetBuilder().build(new FileInputStream("uc_account.xml"));
}
……代码略……
这里要保证uc_account.xml文件能够找到,如果找不到文件,就调整一下这个路径吧。
现在,运行这个单元测试吧。结果是,不管之前的数据是什么样子的,现在运行的结果,表中都只有两条数据。再看看数据库,真的只有两条数据了(刚才要没有备份,现在想恢复就剩下哭了)。
拆解DBUnit
为什么呢?怎么回事数据库就改了?其他测试怎么办呀?
我也这么想,所以将DBUnit的源代码找来拆解重读了一下(英文不好,只能读代码,苦呀)。
原来是这样:我们的测试类继承的DBTestCase首先在newDatabaseTester方法中返回了一个PropertiesBasedJdbcDatabaseTester实例,这个实例通过我们在构造函数中设置的那些环境变量,连接了数据库;然后,在JUnit的测试开始时,调用了setUp方法,这个方法被DBTestCase的某个父类实现后,调用getDataSet提供的数据,清空并复写了数据表中的数据;最后,这才轮到我们的测试方法登场,这时,该删除的删除了,该插入的插入了,所以,我们就看到了那样的结果。
继续拆解一下,其实可以不由环境变量提供数据库连接信息的。DBUnit提供了一些DBTestCase的兄弟类:
JdbcBasedDBTestCase
DataSourceBasedDBTestCase
JndiBasedDBTestCase
其本质是通过newDatabaseTester方法返回一个IDatabaseTester接口的子类,从而提供getConnection和getDataSet等方法的实现。在类库中已经实现的有:
JdbcDatabaseTester
PropertiesBasedJdbcDatabaseTester
DataSourceDatabaseTester
JndiDatabaseTester
甚至你可以自己实现一个,有兴趣的人自己研究吧,我本着够用就好的思想,就不再追究了。
数据提供方法getDataSet也可以使用其他的数据提供方式,总之返回IDataSet的实现就好了。我就不在这里研究了。
数据库有了,数据有了,为什么它是清空数据后插入的数据呢?
又是DBTestCase,它的父类DatabaseTestCase在getSetUpOperation方法中返回了DatabaseOperation.CLEAN_INSERT,这是一个DatabaseOperation的实现,它决定了在单元测试的setUp方法时,调用delete方法删除数据后,调用insert方法插入数据。
同样,DBUnit对于这个部分提供了一些其他的方式:
DatabaseOperation.NONE
DatabaseOperation.REFRESH
DatabaseOperation.INSERT
DatabaseOperation.DELETE
DatabaseOperation.DELETE_ALL DatabaseOperation.TRUNCATE_TABLE
DatabaseOperation.CLEAN_INSERT
需要的时候,我们覆盖getSetUpOperation,返回需要的类就好了。
或者,自己实现一个DatabaseOperation的子类,那就不是这个文章的内容了。我想,通常DatabaseOperation.CLEAN_INSERT应该是够用了。
所以,DBUnit在我们测试开始之前就将数据库清空了,然后插入了它自己的数据。这怎么能行?其他数据也很有用的。不只你这么说,我也这么说。
还有一个问题,数据已经被DBUnit覆盖了,原来的数据怎么在测试后恢复呢?DBUnit给出的办法是在测试开始前,将数据备份,在数据结束后,再将数据恢复。相关的文章网上很多,你可以google一下,或者你跟我一样懒,看一下这篇文章,大概也能了解:
http://www.blogjava.net/iamlibo/archive/2009/03/14/259731.html
我不喜欢这种方案,所以也就不在这里研究了。我只需要DBUnit的数据库初始化能力,在测试开始时候,初始化数据。在这个外面,使用Spring的测试框架,将整个测试作为一个事务,在测试结束后,事务结束并回滚,数据就又恢复回来了。
关于这个想法的实现,将在我下个文章中说明,先预告一下《结合Spring与DBUnit做单元测试》
另外,我不得不承认,想在博客上发表格式比较整齐的文章真麻烦,如果有人想看更整齐的格式,可以去我的文库:
http://wenku.baidu.com/view/218a706bb84ae45c3b358ca7.html?st=1