DbUnit 的数据层测试

来源:互联网 发布:linux curl post get 编辑:程序博客网 时间:2024/05/18 02:25

目的:

初步学习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,它的父类DatabaseTestCasegetSetUpOperation方法中返回了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的测试框架,将整个测试作为一个事务,在测试结束后,事务结束并回滚,数据就又恢复回来了。


关于这个想法的实现,将在我下个文章中说明,先预告一下《结合SpringDBUnit做单元测试》


另外,我不得不承认,想在博客上发表格式比较整齐的文章真麻烦,如果有人想看更整齐的格式,可以去我的文库:


http://wenku.baidu.com/view/218a706bb84ae45c3b358ca7.html?st=1