您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
Spring+iBatis+DBUnit 进行单元测试
 
作者:疯子liu 来源:伯乐在线 发布于 2015-8-27
  3513  次浏览      15
 

在很多成功的软件项目中,测试自动化往往是关键的层面。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 大写

   
3513 次浏览       15
相关文章

微服务测试之单元测试
一篇图文带你了解白盒测试用例设计方法
全面的质量保障体系之回归测试策略
人工智能自动化测试探索
相关文档

自动化接口测试实践之路
jenkins持续集成测试
性能测试诊断分析与优化
性能测试实例
相关课程

持续集成测试最佳实践
自动化测试体系建设与最佳实践
测试架构的构建与应用实践
DevOps时代的测试技术与最佳实践
最新课程计划
信息架构建模(基于UML+EA)3-21[北京]
软件架构设计师 3-21[北京]
图数据库与知识图谱 3-25[北京]
业务架构设计 4-11[北京]
SysML和EA系统设计与建模 4-22[北京]
DoDAF规范、模型与实例 5-23[北京]

LoadRunner性能测试基础
软件测试结果分析和质量报告
面向对象软件测试技术研究
设计测试用例的四条原则
功能测试中故障模型的建立
性能测试综述
更多...   

性能测试方法与技术
测试过程与团队管理
LoadRunner进行性能测试
WEB应用的软件测试
手机软件测试
白盒测试方法与技术

某博彩行业 数据库自动化测试
IT服务商 Web安全测试
IT服务商 自动化测试框架
海航股份 单元测试、重构
测试需求分析与测试用例分析
互联网web测试方法与实践
基于Selenium的Web自动化测试
更多...