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