欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

Spring+iBatis+DBUnit 进行单元测试,ibatisdbunit,在很多成功的软件项目中,

来源: javaer 分享于  点击 15849 次 点评:206

Spring+iBatis+DBUnit 进行单元测试,ibatisdbunit,在很多成功的软件项目中,


在很多成功的软件项目中,测试自动化往往是关键的层面。DBUnit允许开发人员在测试之前给目标数据库植入测试数据,在测试完毕后,再将数据库恢复到测试前的状态。在最近的一个项目中,我尝试使用用DBUnit对Spring+iBatis的架构进行测试,下面记录了DBUnit的使用过程和遇到的一些问题。

测试环境

首先,我们建立一个测试环境(基于Maven 2和Oracle数据库*)。数据表名Account。

数据库

先建立一个测试数据表(数据库为Oracle*)

Account.sql

CREATE TABLE Account("ID" NUMBER,  "USERNAME" VARCHAR2(256 BYTE) NOT NULL ENABLE,  "PASSWORD" VARCHAR2(256 BYTE),    CONSTRAINT "ACCOUNT_UK_ID" UNIQUE ("ID"),    CONSTRAINT "ACCOUNT_PK" PRIMARY KEY ("USERNAME"))

这里我暂时不想涉及Sequence,所以主键**是username,而不是ID,并且ID允许为NULL。这是因为Sequence的递增是不可恢复的,如果项目对记录ID是否连续不是特别在意的话,可以在自己的项目中建立,只要稍微修改一下iBatis配置文件中的SQL语句就可以了。这里我们先屏蔽这个问题。

DBUnit测试Oracle数据库时,帐户最好不要拥有DBA权限,否则会出现org.dbunit.database.AmbiguousTableNameException: COUNTRIES 错误。如果帐户必须具备DBA权限,那么就需要在执行new DatabaseConnection时,明确给定SCHEMA(名称必须大写),详细说明参考下文多处代码注释和“org.dbunit.database.AmbiguousTableNameException异常”章节。

表必须存在主键,否则返回org.dbunit.dataset.NoPrimaryKeyException错误。

Spring配置文件

ApplicationContext.xml

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"   "http://www.springframework.org/dtd/spring-beans.dtd"><beans>   <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">       <property name="locations">           <list>               <value>classpath:database.properties</value>           </list>       </property>   </bean>   <bean id="dataSource"      class="org.springframework.jdbc.datasource.DriverManagerDataSource">      <property name="driverClassName" value="${database.connection.driver_class}"/>      <property name="url" value="${database.connection.url}"/>      <property name="username" value="${database.connection.username}"/>      <property name="password" value="${database.connection.password}"/>   </bean>   <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">      <property name="configLocation">          <value>SqlMapConfig.xml</value>      </property>      <property name="dataSource" ref="dataSource"/>   </bean>   <bean id="accountManager" class="com.wang.dbunit.AccountManager">      <property name="sqlMapClient" ref="sqlMapClient"/>   </bean></beans>
database.propertiesdatabase.connection.driver_class=oracle.jdbc.driver.OracleDriverdatabase.connection.url=jdbc:oracle:thin:@xxx.xxx.xxx.xxx:1521:testdatabase.connection.username=usernamedatabase.connection.password=password

iBatis配置文件

SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE sqlMapConfig   PUBLIC"-//iBATIS.com//DTD SQL Map Config 2.0//EN"   "http://www.ibatis.com/dtd/sql-map-config-2.dtd"><sqlMapConfig>   <settings      useStatementNamespaces="false"      cacheModelsEnabled="true"      enhancementEnabled="true"      lazyLoadingEnabled="true"      maxRequests="32"      maxSessions="10"      maxTransactions="5"   />   <sqlMap resource="Account.xml"/></sqlMapConfig>

Account.xml

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPEsqlMap   PUBLIC"-//iBATIS.com//DTD SQL Map 2.0//EN"   "http://www.ibatis.com/dtd/sql-map-2.dtd"><sqlMap namespace="Account">   <resultMap id="accountMap" class="com.wang.dbunit.Account">      <result property="id" column="id" jdbcType="NUMBER" nullValue="0"/>      <result property="userName" column="username" jdbcType="VARCHAR2"/>       <result property="password" column="password" jdbcType="VARCHAR2"/>   </resultMap>   <!--** preserve ************************************** -->   <sql id="id-select">      <![CDATA[       SELECT id_sequence.nextval AS id FROM dual      ]]>   </sql>   <!--*************************************************** -->   <sql id="account-select">      <![CDATA[       SELECTid, username      ]]>      <dynamic prepend=",">          <isEqual           property="includePassword"           compareValue="true">            password          </isEqual>      </dynamic>      FROMaccount   </sql>   <sql id="account-where">      <![CDATA[       username=#userName:VARCHAR2#      ]]>      <dynamic>          <isNotNull           property="password"           prepend="AND ">             <![CDATA[              password=#password:VARCHAR2#             ]]>          </isNotNull>      </dynamic>   </sql>   <select id="getAccount"    parameterClass="com.wang.dbunit.Account"    resultMap="accountMap">      <include refid="account-select"/>      <dynamic prepend=" WHERE">          <isNotNull           property="userName">             <include refid="account-where"/>          </isNotNull>      </dynamic>   </select>   <!--**************************************************** -->   <sql id="account-insert">      <![CDATA[       INSERT INTO account(username, password      ]]>      <dynamic prepend=",">          <isNotEqual           property="id"           compareValue="0">          <![CDATA[            id         ]]>          </isNotEqual>      </dynamic>      )   </sql>   <sql id="account-insert-values">      <![CDATA[       VALUES(#userName:VARCHAR2#, #password:VARCHAR2#      ]]>      <dynamic prepend=",">          <isNotEqual           property="id"           compareValue="0">          <![CDATA[            #id:NUMBER#         ]]>          </isNotEqual>      </dynamic>      )   </sql>   <insert id="createAccount"    parameterClass="com.wang.dbunit.Account">      <isEqual       property="generateIdFromSequence"       compareValue="true">          <include refid="id-select"/>      </isEqual>      <include refid="account-insert"/>      <include refid="account-insert-values"/>   </insert></sqlMap>

(这个配置文件中预留了未来使用 sequence 的可能)

DBUnit配置文件

我们通过一个xml种子文件(seedfile)为DBUnit提供测试数据,文件中的数据会被DBUnit在测试开始前自动植入数据表,这个文件结构很简单:

dataSet.xml

<?xml version='1.0' encoding='UTF-8'?><DATASET>   <ACCOUNT id='1'      username='Drew'      password='Smith'/>   <ACCOUNT id='2'      username='Nick'      password='Marquiss'/>   <ACCOUNT id='3'      username='Jose'      password='Whitson'/></DATASET>

“ACCOUNT”就是表名称,它的属性就是字段内容。

代码

辅助类Accout.java

package com.wang.dbunit;public class Account{   private boolean generateIdFromSequence=false;   private boolean includePassword = false;   private long id = 0;   private String userName = null;   private String password = null;   public boolean getGenerateIdFromSequence()   {      return generateIdFromSequence;   }   public void setGenerateIdFromSequence(boolean generateIdFromSequence)   {      this.generateIdFromSequence =generateIdFromSequence;   }   public void setId(long id)   {      this.id =id;   }   public long getId()   {      return this.id;   }   public String getPassword()   {      return password;   }   public void setPassword(String password)   {      this.password =password;   }   public String getUserName()   {      return userName;   }   public void setUserName(String userName)   {      this.userName =userName;   }   public boolean isIncludePassword()   {      return includePassword;   }   public void setIncludePassword(boolean includePassword)   {      this.includePassword =includePassword;   }}

业务类AccountManager.java

package com.wang.dbunit;import com.ibatis.sqlmap.client.SqlMapClient;public class AccountManager{   protected SqlMapClient sqlMap = null;   public void setSqlMapClient(SqlMapClient sqlMapClient)   {      this.sqlMap =sqlMapClient;   }   public Account getAccount(String userName, String password, boolean includePassword) throws Exception   {      Account account = new Account();      account.setUserName(userName);      account.setPassword(password);      account.setIncludePassword(includePassword);      Account ret         = (Account)(sqlMap.queryForObject("getAccount", account));      return ret;   }   public void createAccount(String userName, String password) throws Exception   {      Account account = new Account();      account.setUserName(userName);      account.setPassword(password);      sqlMap.insert("createAccount",account);   }}

好了,我们完成了了全部测试环境,接下来我们要开始编写测试用例。

测试

DatabaseTestCase类

DBUnit提供了一个抽象类: DatabaseTestCase,它继承自 JUnit的 TestCase,这个类有两个方法需要重载:

protecte abstract IDatabaseConnection getConnection() throws Exception;protected abstract IDataSet getDataSet() throws Exception;

getConnection用于告诉测试用例如何找到数据库连接;getDataSet用于告诉测试用例如何获取测试数据文件(dataSet.xml)。下面我们先继承这个抽象类编写测试用例:

package com.wang.dbunit;import java.io.FileInputStream;import java.sql.Connection;import java.util.Properties;import javax.sql.DataSource;import com.wang.dbunit.Account;import org.apache.log4j.Logger;import org.apache.log4j.LogManager;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.dbunit.DatabaseTestCase;import org.dbunit.database.DatabaseConnection;import org.dbunit.database.IDatabaseConnection;import org.dbunit.dataset.IDataSet;import org.dbunit.dataset.xml.FlatXmlDataSet;import org.dbunit.operation.DatabaseOperation;public class HelloDBUnit extends DatabaseTestCase{   static Logger logger      = LogManager.getLogger(HelloDBUnit.class.getName());   privatestatic ApplicationContext context;   protected Properties props = new Properties();   public HelloDBUnit() throws IOException   {      super();      props.load(Resources.getResourceAsStream(         "database.properties"));      context = newClassPathXmlApplicationContext(         "classpath:ApplicationContext.xml");   }   ////////////////////////////////////////////////   @Override   protected IDatabaseConnection getConnection() throws Exception   {      DataSourcedataSource      = (DataSource)context.getBean("dataSource");      Connectionconnection = dataSource.getConnection();      // 如果所用测试帐户是 DBA,为了避免出现 AmbiguousTableNameException      // 异常,下面必须改写为 newDatabaseConnection(connection, SCHEMA)      // 形式。注意SCHEMA 要大写**      return new DatabaseConnection(connection);   }   @Override   protected IDataSet getDataSet()throws Exception   {      return new FlatXmlDataSet(               new FileInputStream("bin/dataSet.xml"));   }   ///////////////////////////////////////////////   @Override   protected DatabaseOperation getSetUpOperation() throws Exception   {      return DatabaseOperation.REFRESH;   }   @Override   protected DatabaseOperation getTearDownOperation() throws Exception   {      return DatabaseOperation.NONE;   }   ////////////////////////////////////////////////////   public void testSelectAccount()   {      AccountManager manager         = (AccountManager)context.getBean("accountManager");      try      {         Accountaccount            = manager.getAccount("Nick", "Marquiss", true);         assertNotNull(account);      }      catch (Exceptione)      {         logger.error(e.getMessage(),e);      }   }   public void testCreateAccount()   {      AccountManager manager         = (AccountManager)context.getBean("accountManager");      try      {          manager.createAccount("TEST", "test");      }      catch(Exception e)      {          logger.error(e.getMessage(),e);      }   }}

在getConnection方法中,我们通过Spring配置文件获得数据连接。

除了前面那两个方法外,我们还重载了 getSetUpOperation 和 getTearDownOperation 方法:DatabaseOperation.REFRESH 告诉DBUnit在测试开始前比较数据库和配置文件,如果发现测试数据不存或不一致在则插入或更新***。DatabaseOperation.NONE表示什么也不做。

这个CASE应该可以运行的很好,但是如果我们把 getTearDownOperation改成:

@Overrideprotected DatabaseOperation getTearDownOperation() throws Exception{   return DatabaseOperation.DELETE_ALL;}

就会发生java.sql.SQLException: Closed Connection异常。这是为什么呢?问题出在DatabaseTestCase中。

参数含义

DatabaseOperation.CLEAN_INSERT; 先删除表中所有,再插入准备的数据DatabaseOperation.REFRESH; 使用准备数据更新表,存在则update,不存在则insertDatabaseOperation.DELETE; 只删除准备的数据DatabaseOperation.DELETE_ALL 清除所有记录DatabaseOperation.NONE; 啥都不做

java.sql.SQLException: Closed Connection异常

来看一下DatabaseTestCase的一个关键成员变量tester和有关的一些方法:

public abstract class DatabaseTestCase extends TestCase{   ......   private IDatabaseTester tester;   ......   protected IDatabaseTester getDatabaseTester() throws Exception {      if (this.tester == null) {         this.tester = newDatabaseTester();      }      return this.tester;   }   ......   protected IDatabaseTester newDatabaseTester() throws Exception{      logger.debug("newDatabaseTester()- start");      // 重载的 getConnection 方法,在 IDatabaseTester 里有一个同名方法。      // 注意区分。      final IDatabaseConnection connection = getConnection();      final IDatabaseTester tester         = new DefaultDatabaseTester(connection);      return tester;   }   ......   protected void setUp() throws Exception   {      logger.debug("setUp()- start");      super.setUp();      final IDatabaseTester databaseTester = getDatabaseTester();      assertNotNull("DatabaseTesteris not set", databaseTester);      databaseTester.setSetUpOperation(getSetUpOperation());      databaseTester.setDataSet(getDataSet());      databaseTester.onSetup();   }   ......}

可见 DatabaseTestCase 内部有一个 IDatabaseTester 接口的实例(tester),实际上所有的测试工作是由它完成的。而DatabaseTestCase的newDatabaseTester方法在生成这个实例的时候用的是DefaultDatabaseTester。传入一个由重载的getConnection方法返回的IDatabaseConnection实例。

DefaultDatabaseTester记录了这个连接实例后,提供了一个同名的getConnection()方法(不是DatabaseTestCase中被重载的那个getConnection),用来返回它:

public class DefaultDatabaseTester extends AbstractDatabaseTester{   final IDatabaseConnection connection;   public DefaultDatabaseTester(final IDatabaseConnection connection){      this.connection= connection;   }   public IDatabaseConnection getConnection() throws Exception {      return this.connection;   }}

因为所有的IDatabaseTester实现(包括DefaultDatabaseTester)都继承自AbatractDatabaseTester,这个抽象类有一个统一的执行数据库操作的方法executeOperation,原代码如下:

private void executeOperation(DatabaseOperation operation) throws Exception{   logger.debug("executeOperation(operation={})- start", operation);   if(operation != DatabaseOperation.NONE ){      // IDatabaseTester 的 getConnection 方法,不是重载的那个。      IDatabaseConnection connection = getConnection();      try{          operation.execute(connection, getDataSet() );      }      finally{         closeConnection(connection);      }   }}

我们看到每执行完一次操作,数据库连接都会被关闭,所以如果继承DefaultDatabaseTester,将导致只能执行一次数据库操作。

如果希望在一个TestCase里执行两次操作,我们可以使用另一个基类

DBTestCase类

如上面所看到的,问题出在DatabaseTestCase的newDatabaseTester方法返回了一个无法重复利用的DefaultDatabaseTester实例,所以DBTestCase的newDatabaseTester方法代码变更如下:

protected IDatabaseTester newDatabaseTester() throws Exception {   return new PropertiesBasedJdbcDatabaseTester();}

它用来生成实例的是 PropertiesBasedJdbcDatabaseTester 类,而不是 DefaultDatabaseTester 。这个类的父类 JdbcDatabaseTester(也继承自 AbstractDatabaseTester)的 getConnection 方法:

public IDatabaseConnection getConnection() throws Exception{   logger.debug("getConnection() - start");   if(!initialized ){      // 注意这个方法,等一下详解      initialize();   }   assertNotNullNorEmpty("connectionUrl", connectionUrl);   Connection conn = null;   if(username == null && password == null ){      conn = DriverManager.getConnection(connectionUrl);   }else{      Conn = DriverManager.getConnection(connectionUrl,username,password);   }   return new DatabaseConnection( conn, getSchema() );}

可以看到每次调用这个方法,都会新建一个连接,而不是简单的返回我们重载的 getConnection 中返回的连接的引用。这样就避免了 DefaultDatabaseTester 仅仅是简单返回之前的连接而倒置的问题。不过这也意味着用 DBTestCase 就不用我们自己去重载 getConnection 了,因为 DBTestCase 已经实现了这个方法(DatabaseTestCase没有实现):

protected IDatabaseConnection getConnection() throws Exception {   logger.debug("getConnection() - start");   final IDatabaseTester databaseTester = getDatabaseTester();   assertNotNull( "DatabaseTester is not set",databaseTester);   return databaseTester.getConnection();}

我们看到DBTestCase的getConnection简单的把这个方法转给JdbcDatabaseTester(IDatabaseTester) 的getConnection。而JdbcDatabaseTester的实现我们在前面已经看到了。

现在我们把DatabaseTestCase替换成DBTestCase,并且注释掉HelloDBUnit中重载的getConnection(不再需要了,父类DBTestCase中已经实现了该方法。)然后执行,我们又遇到一个异常:

org.dbunit.AbstractDatabaseTester$AssertionFailedError:driverClass is null

这是因为PropertiesBasedJdbcDatabaseTester的initialize方法(见上面代码)用来初始化数据库连接参数:

protected void initialize() throwsException{   logger.debug("initialize() - start");   setDriverClass(System.getProperty(DRIVER_CLASS));   setConnectionUrl(System.getProperty(CONNECTION_URL));   setUsername(System.getProperty(USERNAME));   setPassword(System.getProperty(PASSWORD));   // 如果所用测试帐户是 DBA,为了避免出现 AmbiguousTableNameException   // 异常,必须明确给出 SCHEMA。注意 SCHEMA要大写**   // setSchema(System.getProperty(SCHEMA));   super.initialize();}

从这里我们看到DBTestCase需要从系统变量中获得连接参数,所以我们必须修改HelloDBUnit的构造函数,将配置参数读入系统变量:

public HelloDBUnit() throws IOException{   super();      props.load(Resources.getResourceAsStream("database.properties"));      if(null == context)      {         context = new ClassPathXmlApplicationContext(            "classpath:ApplicationContext.xml");      }   System.setProperty(      PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS,      props.getProperty("database.connection.driver_class"));   System.setProperty(      PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL,      props.getProperty("database.connection.url"));   System.setProperty(      PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME,      props.getProperty("database.connection.username"));   System.setProperty(      PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD,      props.getProperty("database.connection.password"));}

现在可以正确执行了。不过,这依然存在遗憾,既然我们使所有配置参数都文本化了,就不希望看到

   System.setProperty(      PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS,      props.getProperty("database.connection.driver_class"));

这样的代码,因为如果我们有多个数据源,或改变了配置文件,我们不得不还需要改变测试用例的代码。可以用DataSourceBasedDBTestCase 来解决这个问题。

DataSourceBasedDBTestCase类DataSourceBasedDBTestCase 抽象类要求子类实现 getDataSource 方法以获取外部数据源。

首先,改变父类为 DataSourceBasedDBTestCase 。然后移除构造函数中关于系统变量的设置。最后加入以下代码:

@Overrideprotected DataSource getDataSource(){   return(DataSource)context.getBean("dataSource");}

org.dbunit.database.AmbiguousTableNameException异常

很遗憾, DataSourceBasedDBTestCase 没有提供设置 SCHEMA 的方法,虽然它的成员变量 DataSourceBasedDBTester 通过继承了 AbstractDatabaseTester 而提供了 setSchema 方法,但是因为所有的 IDatabaseTester 都是 Private 变量,因此实际上我们无法访问到它的 setSchema。所以如果使用 DataSourceBasedDBTestCase ,除非重载 setUp 和tearDown方法,否则就不能有DBA权限。

protected void setUp() throws Exception{   DataSource dataSource = getDataSource();   Connection connection = dataSource.getConnection();   IDatabaseConnection dbUnitCon      = new DatabaseConnection(connection, "SYSTEM");   if(getDataSet() != null)   {      try      {          getSetUpOperation().execute(dbUnitCon, getDataSet());      }      finally      {          if(connection!= null)             connection.close();      }   }}protected void tearDown() throws Exception{   DataSource dataSource = getDataSource();   Connection connection = dataSource.getConnection();   IDatabaseConnection dbUnitCon      = new DatabaseConnection(connection, "SYSTEM");   if(getDataSet()!= null)   {      try      {          getTearDownOperation().execute(dbUnitCon,getDataSet());      }      finally      {          if(connection!= null)             connection.close();      }   }}

支持事务回滚

虽然DBUnit提供了一些方法让我们可以在测试开始和结束时清理数据库,但是有时候依然不能满足需求,比如在上面的代码中,我们在执行阶段插入了一条记录(见testCreateAccount方法),这种不是在种子文件中的额外数据,测试结束后除非在tearDown中返回DatabaseOperation.DELETE_ALL,否则是不会被自动删除的,可是如果删除全部数据,那么又有可能删掉了不希望删掉的数据。Spring提供了一个AbstractTransactionalDataSourceSpringContextTests测试类,这个类可以在测试结束后回滚数据库,可是DBUnit没有提供类似的机制,所以我们要进一步手工扩展测试用例,以加入类似功能。

修改ApplicationContext.xml

首先,修改Spring的配置文件ApplicationContext.xml,加入以下配置:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">   <property name="dataSource">      <ref bean="dataSource"/>   </property></bean><bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">   <property name="transactionManager">      <ref bean="transactionManager"/>   </property>   <property name="transactionAttributes">      <props>          <prop key="*">PROPAGATION_REQUIRED</prop>      </props>   </property></bean>

如果希望业务类也支持事务处理可以加入以下配置,否则可以略过:

<bean id="accountManagerTarget" class="com.wang.dbunit.AccountManager">   <property name="sqlMapClient" ref="sqlMapClient"/></bean><bean id="accountManager" class="org.springframework.aop.framework.ProxyFactoryBean">   <property name="proxyInterfaces">      <value>          com.wang.dbunit.IAccountManager      </value>   </property>   <property name="interceptorNames">      <list>          <idref local="transactionInterceptor" />          <idref local="accountManagerTarget" />      </list>   </property></bean>

以上配置只作用于使业务类,因为我们的测试用例类“HelloDBUnit.java”没有出现在配置文件中,更没有设置任何拦截器,所以测试用例对数据库的所有操作(插入、清除测试数据)目前都不在拦截范围。我们必须在测试用例中手工为它加入事务处理,才可以达到我们的目的。

添加事务管理代码

添加以下属性和方法:

private TransactionStatus ts = null;private DataSourceTransactionManager transactionManager = null;......protected void setUpTransaction(){   transactionManager      =(DataSourceTransactionManager)context.getBean(         "transactionManager");   TransactionDefinition td = new DefaultTransactionDefinition();   ts = transactionManager.getTransaction(td);}protected void tearDownTransaction(boolean commit){   if(commit)   {      transactionManager.commit(ts);   }   else   {      transactionManager.rollback(ts);   }}

修改setUp和tearDown方法:

protected void setUp() throws Exception{   setUpTransaction();   DataSourced ataSource = getDataSource();   // 替换 Connection connection = dataSource.getConnection();   Connection connection = DataSourceUtils.getConnection(dataSource);   IDatabaseConnection dbUnitCon      = new DatabaseConnection(connection, "SYSTEM");   if(getDataSet()!= null)   {      try      {         getSetUpOperation().execute(dbUnitCon, getDataSet());      }      finally      {         if(connection!= null)         {            // 替换 connection.close();            DataSourceUtils.releaseConnection(               connection, dataSource);          }      }   }}protected void tearDown() throws Exception{   DataSource dataSource = getDataSource();   // 替换 Connection connection = dataSource.getConnection();   Connection connection = DataSourceUtils.getConnection(dataSource);   IDatabaseConnection dbUnitCon      = new DatabaseConnection(connection, "SYSTEM");   if(getDataSet() != null)   {      try      {          // 如果不希望回滚数据,传入 true 参数。          tearDownTransaction(false);          getTearDownOperation().execute(dbUnitCon, getDataSet());      }      finally      {         if(connection!= null)         {            // 替换 connection.close();            DataSourceUtils.releaseConnection(               connection, dataSource);          }      }   }}

最后修改getTearDownOperation,用 DELETE 替换 DELETE_ALL:

protected DatabaseOperation getTearDownOperation() throws Exception{    return DatabaseOperation.DELETE;}

现在在表中随便添加一些记录,然后执行我们的测试用例,执行完后,手工添加的数据没有受到任何影响。

最后一点提示:

因为每一个 testXxx 方法时都会初始化一个测试用例,所以每执行一次 testXxxx 方法都会导致 setUp 和 tearDown 方法被调用一次,而本例的事务定义也都由 setUp 开始, tearDown 结束,也就意味着不同测试方法(同一测试用例)之间处于不同的事务范围,导致数据操作结果无法共享(如果每次 tearDown 都回滚数据)。比如在 testInsert 方法中插入数据,在 testSelect 无法获得。要解决这个问题,就要适当的修改对 setUpTransaction 和 dearDownTransaction 的调用,使得事务可以在全局范围内被多个 test 方法共享。

E:\work\bpm2_v1_00\test.xml:171: org.dbunit.dataset.NoSuchTableException: VERSION_INFO

DBunit 要求schema 大写

相关栏目:

用户点评