好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

用DbUnit进行数据库集成测试

用DbUnit进行数据库集成测试

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>  <Person  Name  =  "Kirin"  Age  =  "31"  Location  =  "Beijing"  />  <Person  Name  =  "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文件

我们可以手写XML来准备数据,也可以从数据库中导出现有的数据,用 FlatXmlDataSet.write() 静态方法即可,例如:

  QueryDataSet   dataSet   =  new  QueryDataSet  (  connection  );  
dataSet  .  addTable  (  TABLE_NAME  ,  "select * from "  +   TABLE_NAME  );  
dataSet  .  addTable  (...);  FlatXmlDataSet  .  write  (  dataSet  ,  new  FileOutputStream  (  "data.xml"  ));  

重写getDataSet

有了文件数据,我们就需要重写 getDataSet() ,让它加载文件中的数据并返回。

  @Override  protected  IDataSet   getDataSet  ()  throws  Exception  {  // set column sensing as true, so it can dynamically and columns with null value.   return  new  FlatXmlDataSetBuilder  ()  .  setColumnSensing  (  true  )  .  build  (  new  FileInputStream  (  "data.xml"  ));  }  
IDatabaseTester

DBTestCase 重写了 getConnection() ,并把它设置为final,将获取connection的操作委托给 IDatabaseTester ,我们可以通过重写 getDatabaseTester() 方法来设置具体的 IDatabaseTester 。Dbunit中, IDatabaseTester 的实现类一共有四个:

DefaultDatabaseTester JdbcDatabaseTester DataSourceDatabaseTester JndiDatabaseTester

它们的用途不言自明。

DatabaseTestCase 重写了 TestCase 里的 setUp() 和 tearDown() 方法。

  protected  void   setUp  ()  throws  Exception  {  super  .  setUp  ();  final  IDatabaseTester   databaseTester   =   getDatabaseTester  ();  
    assertNotNull  (  "DatabaseTester is not set"  ,   databaseTester   );  
    databaseTester  .  setSetUpOperation  (   getSetUpOperation  ()  );  
    databaseTester  .  setDataSet  (   getDataSet  ()  );  
    databaseTester  .  setOperationListener  (  getOperationListener  ());  
    databaseTester  .  onSetup  ();  }  protected  void   tearDown  ()  throws  Exception  {  try  {  final  IDatabaseTester   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

DatabaseOperation 定义了对数据库进行的操作,它是一个抽象类,通过静态字段提供了几种内置的实现:

NONE:不执行任何操作,是 getTearDownOperation 的默认返回值。 UPDATE:将数据集中的内容更新到数据库中。它假设数据库中已经有对应的记录,否则将失败。 INSERT:将数据集中的内容插入到数据库中。它假设数据库中没有对应的记录,否则将失败。 REFRESH:将数据集中的内容刷新到数据库中。如果数据库有对应的记录,则更新,没有则插入。 DELETE:删除数据库中与数据集对应的记录。 DELETE_ALL:删除表中所有的记录,如果没有对应的表,则不受影响。 TRUNCATE_TABLE:与DELETE_ALL类似,更轻量级,不能rollback。 CLEAN_INSERT:是一个组合操作,是DELETE_ALL和INSERT的组合。是 getSetUpOeration 的默认返回值。

由此我们可以总结出,在一个测试执行前后,DbUnit会为我们做哪些工作:

移除数据库中的所有记录(CLEAN_INSERT中的DELETE_ALL)。 将数据集中的数据加载到数据库中(CLEAN_INSERT中的INSERT)。 运行测试。 测试运行完毕后,不执行任何操作。

我们可以根据需要,在测试类中重写 setUp 和 tearDown ,以实现定制的需求。比如,数据库中已经有一些数据,我们不希望数据集中的数据对它们产生任何影响,这时可以先将数据库中的数据备份到内存中,等测试完成后再恢复到数据库中,代码如下:

  private  IDataSet   dataSetBackup  ;  private  static  final  String  []   TABLE_NAMES   =  new  String  []  {  "..."  };  @Override  protected  void   setUp  ()  throws  Exception  {  
    dataSetBackup   =  new  CachedDataSet  (  getConnection  ().  createDataSet  (  TABLE_NAMES  ));  super  .  setUp  ();  }  @Override  protected  void   tearDown  ()  throws  Exception  {  try  {  final  IDatabaseTester   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  }  }  @Override  protected  DatabaseOperation   getTearDownOperation  ()  {  return  DatabaseOperation  .  CLEAN_INSERT  ;  }  

测试前用CLEAN_INSERT,是用数据集覆盖数据库,测试后用CLEAN_INSERT,使用备份的数据库覆盖之前插入到数据库中的数据集。

完整的基类代码在 这里 。

好了,现在可以开始测试了。

 

分类:  [08] 数据库技术 ,  [11] 日积月累

标签:  dbunit ,  integration-testing

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于用DbUnit进行数据库集成测试的详细内容...

  阅读:44次

上一篇: Ruby开发集成环境

下一篇:Thrift