jtester与dbunit冲突问题解决!

Levit应用中存在Jtester和DBUnit两种单元测试框架,在信联项目中发现2个框架编写的单元测试同时跑会发生冲突导致单元测试不过。 当时由于项目时间紧张采取了临时解决方法,Jtester与dbunit单元测试分开跑来规避了此问题。。。没有解决根本问题,现在就一步一步的去探索究 竟是什么问题引起的。。。

1、问题重现看一下报错信息

 执行命令:

mvn clean compile test

错误信息如下:


为什么会有46个错误呀,好恐怖呀,打开TestSuite.txt看一下具体是什么问题,部分错误信息如下:

testFind(com.alibaba.china.levit.biz.guarantee.dal.dao.test.AcFreezeDAOTest)  Time elapsed: 6.934 sec  <<< FAILURE!

junit.framework.AssertionFailedError: null


at com.alibaba.china.levit.biz.guarantee.dal.dao.test.AcFreezeDAOTest.testFind(AcFreezeDAOTest.java:127)



testCountFreezeAndFreezeForever(com.alibaba.china.levit.biz.guarantee.dal.dao.test.AcFreezeDAOTest) Time elapsed: 7.188 sec <<< FAILURE!

junit.framework.AssertionFailedError: expected:<8> but was:<121>

at com.alibaba.china.levit.biz.guarantee.dal.dao.test.AcFreezeDAOTest.testCountFreezeAndFreezeForever(AcFreezeDAOTest.java:283)



testQueryBySearchParam(com.alibaba.china.levit.biz.guarantee.dal.dao.test.AcFreezeDAOTest) Time elapsed: 7.276 sec <<< FAILURE!

junit.framework.AssertionFailedError: expected:<1> but was:<0>

at com.alibaba.china.levit.biz.guarantee.dal.dao.test.AcFreezeDAOTest.testQueryBySearchParam(AcFreezeDAOTest.java:300)



testCountTrade(com.alibaba.china.levit.biz.guarantee.dal.dao.test.AcFreezeDAOTest) Time elapsed: 7.353 sec <<< FAILURE!

junit.framework.AssertionFailedError: expected:<2> but was:<0>

at com.alibaba.china.levit.biz.guarantee.dal.dao.test.AcFreezeDAOTest.testCountTrade(AcFreezeDAOTest.java:312)

看以上的报误信息大概可以分析出来是null值导致单元测试不过的。。。

2、定位错误

 因为只有通过mvn test 才能重现这个问题,打开 mvn test远程调试端口,一步步跟...

1、方法一:

  mvn -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -Xnoagent -Djava.compiler=NONE"

test

2、方法二:

修改pom.xml文件,如下代码:

<configuration>

<argLine>-javaagent:"${settings.localRepository}/com/alibaba/external/test.jmockit/0.997/test.jmockit-0.997.jar"
</argLine>

<junitArtifactName>com.alibaba.external:test.junit</junitArtifactName>

<testNGArtifactName>com.alibaba.external:test.testng.jdk15</testNGArtifactName>

<debugForkedProcess>true
</debugForkedProcess>

</configuration>

默认端口为:5005
http://maven.apache.org/plugins/maven-surefire-plugin/examples/debugging.html
基于dbUnit编写的测试用例

AcFreezeDAOTest代码如下:

 public

class AcFreezeDAOTest extends
DbUnitSpringTransactionalTestCase {



private
static
final
String
AC_ACFREEZE_SET = "dbunit/acFreezeSet001.xml"
;



private
AcFreezeDAO acFreezeDAO;



/**

* 测试 查找赔付金记录

*

* @throws
Exception

*/

public
void testFind() throws
Exception {

/关键部分代码,根据xml配置文件构造一个数据集到内存中。




setUpDataSet("dbunit/acFreezeSet.xml"
);



AcFreezeDO acFreezeDO = null
;



acFreezeDO = this
.acFreezeDAO.find(2L);

Assert.assertNotNull(acFreezeDO);

Assert.assertEquals(acFreezeDO.getAliFreezeMoney().getCent(), 34);

Assert.assertEquals(acFreezeDO.getMemberFreezeMoney().getCent(), 344);

Assert.assertEquals(acFreezeDO.getBuyerMemberId(), "maomaotest03"
);

Assert.assertEquals(acFreezeDO.getSellerMemberId(), "maomaotest02"
);

Assert.assertEquals(acFreezeDO.getTradeNo(), "000002342342"
);

Assert.assertEquals(acFreezeDO.getTradeType(), "alipay001"
);

Assert.assertEquals(acFreezeDO.getGntFundStatus(), "FREE"
);

Assert.assertEquals(acFreezeDO.getIsFreezeCredit(), FreezeCreditState.FREEZE_CREDIT.getState());

Assert.assertEquals(acFreezeDO.getId().longValue(), 2L);

Assert.assertEquals(DateUtil.toLocaleString(acFreezeDO.getGmtActualUnfreeze(), DATE_FORMAT), "2010-01-20"
);

Assert.assertEquals(DateUtil.toLocaleString(acFreezeDO.getGmtAgreedUnfreeze(), DATE_FORMAT), "2010-01-28"
);

Assert.assertEquals(DateUtil.toLocaleString(acFreezeDO.getGmtCreate(), DATE_FORMAT), "2010-01-25"
);

Assert.assertEquals(DateUtil.toLocaleString(acFreezeDO.getGmtModified(), DATE_FORMAT), "2010-01-25"
);

}





public
AcFreezeDAO getAcFreezeDAO() {

return
acFreezeDAO;

}



public
void setAcFreezeDAO(AcFreezeDAO acFreezeDAO) {

this
.acFreezeDAO = acFreezeDAO;

}

}

 

DbUnitSpringTransactionalTestCase 类代码 :

public
class DbUnitSpringTransactionalTestCase extends
SpringTransactionalBaseTestCase {

/**

* 设置数据源

*/

SchemaAwareDataSourceProxy dataSource;

/**

* dbunit dbUnitconn初始化,dbunit和数据库交互使用的连接。

*/

public
IDatabaseConnection dbUnitConn;



/**

* 创建之前需要先初始化dbunit 由spring容器自动创建。

*/

protected
void onSetUpInTransaction() throws
Exception {

super
.onSetUpInTransaction();

// dbUnit使用的数据源


if
(dataSource == null
) {

dataSource = (SchemaAwareDataSourceProxy) this
.applicationContext.getBean("dataSource"
);

}

// 初始化dbUnit连接


initDbunit();

setAnotationUpdataSet();

}



/**

* 初始化dbUnit dbUnitconnection. 数据准备准备好连接基础。

*

* @throws
Exception

*/

protected
void initDbunit() throws
Exception {

if
(StringUtil.isNotBlank(dataSource.getDbSchema())) {

dbUnitConn = new
DatabaseConnection(DataSourceUtils.getConnection(dataSource), dataSource.getDbSchema());

} else
{

dbUnitConn = new
DatabaseConnection(DataSourceUtils.getConnection(dataSource));

}



}



/**

* 初始化数据表 传入需要初始化的数据xml文件。

*

* @param file

* @throws
Exception

*/

protected
void setUpDataSet(String
file) {

try
{

IDataSet dataset = new
FlatXmlDataSet(new
ClassPathResource(file).getFile());

DatabaseOperation.CLEAN_INSERT.execute(dbUnitConn, dataset);

} catch
(Exception e) {

e.printStackTrace();

Assert.fail();

}

}







public
IDatabaseConnection getDbUnitConn() {

return
dbUnitConn;

}



public
void setDataSource(SchemaAwareDataSourceProxy dataSource) {

this
.dataSource = dataSource;

}

}



SpringTransactionalBaseTestCase 类部分代码:

 public
class SpringTransactionalBaseTestCase extends
AbstractTransactionalDataSourceSpringContextTests {



/**

* 传入applicationContext配置文件,配置文件里面是你需要测试的bean和其依赖的bean. 默认从applicationContext.xml取。如果需要替换,子类可以覆盖。

*/

protected
String
[] getConfigLocations() {

return
new
String
[] { "applicationContext.xml"
};

}



}

applicationContext.xml 文件内容:

<bean id="dataSource"


class="com.alibaba.pivot.common.test.SchemaAwareDataSourceProxy"


destroy-method="close"
>

<property name="driverClassName"
value="com.alibaba.china.jdbc.SimpleDriver"
/>

<property name="url"
value="jdbc:oracle:thin:@10.20.36.18:1521:ocndb"
/>

<property name="username"
value="alibaba"
/>

<property name="password"
value="ca"
/>

<property name="dbSchema"
value="ALIBABA"
/>

<property name="clientEncoding"
value="GBK"
/>

<property name="serverEncoding"
value="ISO-8859-1"
/>

</bean>

<!-- smart ibatis sessionFactoryManager定义。 -->

<bean id="sessionFactoryManager"
class="com.alibaba.ibatis.IBatisSessionFactoryManager"
>

<property name="ormConfig"
>

<map>

<entry key="MAIN_DATASOURCE"
>

<ref local="sqlMapClient"
/>

</entry>

</map>

</property>

<property name="transactionTemplates"
>

<map>

<entry key="MAIN_DATASOURCE"
>

<ref local="transactionTemplate"
/>

</entry>

</map>

</property>



<property name="daoDriverClass"


value="com.alibaba.ibatis.smart.advance.AdvanceSmartIBatisDao"
/>

</bean>

<bean id="acFreezeDAO"
class="com.alibaba.china.credit.dal.guarantee.dao.AcFreezeDAO"
/>



acFreezeSet.xml 文件内容:

 <?xml version='1.0' encoding='UTF-8'?>

<dataset>

<CREDIT_GNT_AC_FREEZE ID="1"
SELLER_MEMBER_ID="maomaotest02"


BUYER_MEMBER_ID="maomaotest03"
TRADE_NO="00000000001"
TRADE_TYPE="alipay"


MEMBER_FREEZE_MONEY="344"
ALI_FREEZE_MONEY="34"
GMT_AGREED_UNFREEZE="2010-01-28"


GMT_ACTUAL_UNFREEZE="2010-01-20"
GNT_FUND_STATUS="FREE"
GMT_CREATE="2010-01-25"


GMT_MODIFIED="2010-01-25"
IS_FREEZE_CREDIT="N"
SOURCE_DETAIL="234234234"
/>



<CREDIT_GNT_AC_FREEZE ID="2"
SELLER_MEMBER_ID="maomaotest02"


BUYER_MEMBER_ID="maomaotest03"
TRADE_NO="000002342342"
TRADE_TYPE="alipay001"


MEMBER_FREEZE_MONEY="344"
ALI_FREEZE_MONEY="34"
GMT_AGREED_UNFREEZE="2010-01-28"


GMT_ACTUAL_UNFREEZE="2010-01-20"
GNT_FUND_STATUS="FREE"
GMT_CREATE="2010-01-25"


GMT_MODIFIED="2010-01-25"
IS_FREEZE_CREDIT="Y"
SOURCE_DETAIL="234234234"
/>

</dataset>

 

关键部分代码.

解释:

1、 setUpDataSet("dbunit/acFreezeSet.xml"
); --数据准备,该方法是dbunit 根据xml文件构造 IDataSet到内存中,表名为"CREDIT_GNT_AC_FREEZE"
,数据为上面xml文件数据。

数据源是通过 this
.applicationContext.getBean("dataSource"
);///关键

2、 acFreezeDAO通过spring容器注入。

acFreezeDO = this
.acFreezeDAO.find(2L);

查找id为2的数据,在数据准备阶段已经把数据放到内存中,正常情况下是可以查出来的。

  代码跟到 "acFreezeDO = this.acFreezeDAO.find(2L);"这行代码,返回的DO为null,注:在IDE中单个单元测试是可以通过的。如图:
数据准备已经把ID为2 的值放到内存中了为什么查不出来呢?

debug 发现acFreezeDAO依赖的数据源与dbUnit基类DbUnitSpringTransactionalTestCase所依赖注入的数据源不一致,为什么呢,看图比较:

 acFreezeDAO数据源 依赖 关系图:


 dataSource类型为:BasicDataSource,applicationContext.xml文件中配置的类型为 SchemaAwareDataSourceProxy,这个怎么回事。。。

DbUnitSpringTransactionalTestCase.dataSource 注入的数据源如图:


 
 dataSource类型为:SchemaAwareDataSourceProxy 与applicationContext.xml文件中配置的类型相同,这个是正确的。

疑问,同一个配置文件的数据源为什么取到类型截然不同呢???先保留疑问后面回答。

基于Jtester 编写的测试用例

GroupCreateServiceTest的代码如下:

 public

class GroupCreateServiceTest extends
BaseTestCase {



@SpringBeanByName

GroupCreateService groupCreateService;



@SpringBeanByName

private
AcFreezeDAO acFreezeDAO;



@Test

public
void test() {



代码省略


}



}

 BaseTestCase 类代码:

@SpringApplicationContext( { "applicationContext.xml"
})

public
class BaseTestCase extends
JTester {



public
BaseTestCase(){

// 初始化log4j


URL url = BaseTestCase.class.getClassLoader().getResource("log4j_test.xml"
);

if
(url != null
) {

DOMConfigurator.configure(url);

} else
{

System
.err.println("not found log4jTest.xml in classpath"
);

}

}



}



spring配置文件与dbunit公用相同的文件。



jtester.properties 文件内容

#database.type=h2db

database.only.testdb.allowing=false


database.type=oracle

database.url=jdbc:oracle:thin:@10.20.36.18:1521:ocndb

database.userName=alibaba_ut

database.password=ca

database.schemaNames=ALIBABA_UT

database.driverClassName=com.alibaba.china.jdbc.SimpleDriver

DatabaseModule.Transactional.value.default
=rollback

database.driverJar=/home/liulin/.m2/repository/com/alibaba/shared/headquarters.jdbc.proxy/1.1/headquarters.jdbc.proxy-1.1.jar

dataSource.wrapInTransactionalProxy=false

debug 查看 acFreezeDAO所对应的数据源类型,如下图:

dataSource类型: BasicDataSource,怪了。。。冷静想一下Jtester也自已维护也有一套配置在jtester.properties中他是不是把applicationContext.xml中的dataSource替换掉了呢 ,带着这个疑问继续看Jtester的运行机制。

debug,Jtester启动代码发现,在加载Spring bean时,如果发现bean的id为dataSource 则默认用jtester内部购造一个数据源替换掉applicationContext.xml配置的数据源。看图:

 类名为:org.jtester.unitils.spring.JTesterClassPathXmlApplicationContext ,有兴趣的可以研究一下。

注:jtester默认会去找bean id 为dataSource的数据源,还可以通过jtester.properties来指定,属性如下:

 spring.datasource.name=jtesterDataSource

 有个疑问,在运行AcFreezeDAOTest用例时,属性 acFreezeDAO 对应的数据源类型不是 Spring配置中配置的类型,是不是和jtester有关系 ,看如下代码:

package

org.jtester.unitils.database.util;



public
class JTesterDataSourceFactory implements
DataSourceFactory {



public
DataSource createDataSource() {

this
.checkDoesTestDB();

BasicDataSource dataSource = new
BasicDataSource();

this
.initFactualDataSource(dataSource);



this
.doesDisableDataSource(dataSource);



return
TracerUnitils.tracerDataSource(dataSource);

}



protected
void initFactualDataSource(BasicDataSource dataSource) {

log.info("Creating data source. Driver: "
+ type.getDriveClass() + ", url: "
+ type.getConnUrl() + ", user: "


+ type.getUserName() + ", password: <not shown>"
);

dataSource.setDriverClassName(type.getDriveClass());

dataSource.setUsername(type.getUserName());

dataSource.setPassword(type.getUserPass());

dataSource.setUrl(type.getConnUrl());

}

}

 以上代码是jtester用来替换applicationContext.xml文件中配置的数据源。疑问解开了,是jtester做的怪。又有个疑问,在执行AcFreezeDAOTest用例时spring会创建一个新的上下文为什么 acFreezeDAO对应的数据源没有变化???

疑问定位到smartIbatis 框架上,是不是他做了处理?顺着dao的逻辑,数据源是sessionManager来管理,sessionManager由SessionFactoryManager来创建,看如下代码:

public

class IBatisSessionFactoryManager implements
SessionFactoryManager, InitializingBean {



private
static
final
Logger logger = LoggerFactory.getLogger(IBatisSessionFactoryManager.class);



/**

* 所有DataObject到sqlMapClient对象的反向映射缓存(cache),,为实现快速数据垂直路由。

*/

private
static
Map<String
, String
> dataObject2SqlMapClientMappingCache = new
HashMap<String
, String
>();



private
static
Map<String
, SessionManager> dataSource2SessionManagerCache = new
HashMap<String
, SessionManager>();



private
static
Map<String
, TransactionTemplate> dataObject2TransactionTemplateMappingCache = new
HashMap<String
, TransactionTemplate>();



/**

* dataObject 到 sqlMapClient 映射关系

*/

protected
void initDataObject2SqlMapClientMappingCache() {

// 已经初始化过,直接找到数据库标记




synchronized
(dataObject2SqlMapClientMappingCache) {



/关键部分


           ////第一次初始化 IBatisSessionFactoryManager时会初始化sqlMapClientMapping,以后不管启动多少个Spring上下文都不会初始化,

           ////因为dataObject2SqlMapClientMappingCache是静态的在jvm级别共享数据。

if
(dataObject2SqlMapClientMappingCache.size() <= 0) {



initdataObject2SqlMapClientMapping();

}

}

}



/**

* 初始化 数据类名到 数据源反向映射,以便之后进行快速数据路由。

*/

@SuppressWarnings("unchecked"
)

protected
void initdataObject2SqlMapClientMapping() {

///部分代码省略



        ////// 初始化sqlMap



dataObject2SqlMapClientMappingCache.put(dataObjectClassName, dataSourceKey);

dataObject2TransactionTemplateMappingCache.put(dataObjectClassName, transactionTemplate);

}

   /**

* 根据数据源标识和数据对象名获取对应的 SessionManager,对于IBatis,它封装了对应的SqlMapClient对象。

*

* @param dataSourceFlag

* @param dataObjectClassName

*/

public
SessionManager getSessionManager(String
dataObjectClassName) {



// 水平扩展要支持的话,这里返回的是一个 datasourceKey 数组。


String
datasourceKey = dataObject2SqlMapClientMappingCache.get(dataObjectClassName);

TransactionTemplate transactionTemplate = getTransactionTemplate(dataObjectClassName);



// datasourceKey gotten, get SessionManager from cache


SessionManager sessionManager = dataSource2SessionManagerCache.get(datasourceKey);

if
(sessionManager == null
) {



SqlMapClient sessionFactory = null
;

sessionFactory = (SqlMapClient) datasources.get(datasourceKey);



if
(sessionFactory == null
) {

throw
new
DaoException("SessionFactory instance ( Datasource) not found for
datasourceKey '"



+ datasourceKey + "' of dataObjectClass:"
+ dataObjectClassName);

}

// cache sessionManager


/省略代码


// put in cache




缓存sessionManager对象。




dataSource2SessionManagerCache.put(datasourceKey, sessionManager1);



if
(logger.isInfoEnabled()) logger.info("SessionManager created! DatasourceKey:{} = SessionManager:{},"
,

datasourceKey, sessionManager1);



// get from cache


sessionManager = dataSource2SessionManagerCache.get(datasourceKey);

}



return
sessionManager;

}

}

代码解释:

 1、静态Map变量

dataObject2SqlMapClientMappingCache  缓存dataSourceKey,key为dataObjectClassName



dataSource2SessionManagerCache 缓存sessionManager对象,key为datasourceKey

dataObject2TransactionTemplateMappingCache 缓存transactionTemplate,key为dataObjectClassName

cache是static的,所以在jvm级别是共享的。



2、initDataObject2SqlMapClientMappingCache()方法



初始化 IBatisSessionFactoryManager 类会缓存数据类名到数据源反向映射

3、 getSessionManager()方法

 缓存sessionManager,下次调用直接走cache.

 

  问题的源头找到了,是cache做的怪。。。。

解决方法1:

重写initDataObject2SqlMapClientMappingCache方法,将缓存改成针对Spring上下文级别而不是jvm级别,代码如下:

 public

class IBatisSessionFactoryManagerForTest extends
IBatisSessionFactoryManager {



private
static
final
Logger logger = LoggerFactory.getLogger(IBatisSessionFactoryManagerForTest.class);



/**

* dataObject 到 sqlMapClient 映射关系

*/

protected
void initDataObject2SqlMapClientMappingCache() {

try
{

/清空缓存


Field dataObject2SqlMapClientMappingCache = IBatisSessionFactoryManager.class.getDeclaredField("dataObject2SqlMapClientMappingCache"
);

dataObject2SqlMapClientMappingCache.setAccessible(true
);

dataObject2SqlMapClientMappingCache.set(dataObject2SqlMapClientMappingCache, new
HashMap<String
, String
>());



Field dataSource2SessionManagerCache = IBatisSessionFactoryManager.class.getDeclaredField("dataSource2SessionManagerCache"
);

dataSource2SessionManagerCache.setAccessible(true
);

dataSource2SessionManagerCache.set(dataSource2SessionManagerCache, new
HashMap<String
, String
>());



} catch
(SecurityException e) {

logger.error("", e);

} catch
(NoSuchFieldException e) {

logger.error("", e);

} catch
(IllegalArgumentException e) {

logger.error("", e);

} catch
(IllegalAccessException e) {

logger.error("", e);

}

/重新加载sqlmap


initdataObject2SqlMapClientMapping();

}

}

注:此方法只能做为临时解决方案。

解决方法2:

升级apollo.smart-ibatis-1.0.1,将缓存的级别调整为Spring上下文级别。

与何坤讨论升级smart-ibatis框架,技术需求已经提了。。。

3、成功

终于看到BUILD SUCCESSFUL了。。。


  相关文章:

http://blog.csdn.net/liulin_good/archive/2011/02/21/6198544.aspx

http://wenku.baidu.com/view/dfe98feb6294dd88d0d26b3f.html

maven surefire插件源码:

http://svn.apache.org/repos/asf/maven/surefire/trunk/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值