Spring+iBatis+DBUnit 进行单元测试[转发]

上一篇 / 下一篇  2011-03-29 15:39:46 / 个人分类:单元测试

Spring+iBatis+DBUnit测试详解
在很多成功的软件项目中,测试自动化往往是关键的层面。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.properties
database.connection.driver_class=oracle.jdbc.driver.OracleDriver
database.connection.url=jdbc:oracle:thin:@xxx.xxx.xxx.xxx:1521:test
database.connection.username=username
database.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改成:

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

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

***参数含义
DatabaseOperation.CLEAN_INSERT;      先删除表中所有,再插入准备的数据
DatabaseOperation.REFRESH;              使用准备数据更新表,存在则update,不存在则insert
DatabaseOperation.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 。然后移除构造函数中关于系统变量的设置。最后加入以下代码:

@Override
protected 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 大写google_protectAndRun("render_ads.js::google_render_ad", google_handleError, google_render_ad);

转自:http://zscomehuyue.javaeye.com/blog/846168


TAG: DBUnit ibatis JUnit Junit JUNIT junit Spring spring 单元测试

 

评分:0

我来说两句

Open Toolbar