在编写数据访问层的单元测试时,遇到不少问题,有些问题可以很容易Google到解决方法,而有些只能自己研究解决。这里分享几个典型的问题以及解决方法。
先交代一下用到的测试框架 Spring Test + SpringTestDbUnit + DbUnit。
一、先说一个低级的问题。
Spring通过<jdbc:embedded-database>标签提供对内存数据的支持,形如:
<jdbc:embeded-database id="dataSource" type="HSQL">
可是在启动时,却总是提示错误:
Caused by: org.xml.sax.SAXParseException; lineNumber: 31; columnNumber: 57; cvc-complex-type.2.4.c: 通配符的匹配很全面, 但无法找到元素 'jdbc:embedded-database' 的声明。
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:198)
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:134)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:437)
……
翻来覆去对标签修改了很多次,文档和dtd也看了很多遍,始终没有发现问题。最后无意间看到context文件头部对标签的声明上好像有问题:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:task="http://www.springframework.org/schema/task" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.2.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/tx/spring-jdbc-3.2.xsd">
仔细看了下,原来当时从tx处复制声明时,只是将最后的tx改成了jdbc,却忘记了将路径中tx改为jdbc。更改后,启动正常。所有,如果有同学遇到类似的问题,应该先检查头部。
二、外键关联导致的删除失败。
在刚开始写测试时,每个用例单独运行都没有问题,可是一旦一起运行,就出现下面的异常:
Tests run: 5, Failures: 0, Errors: 3, Skipped: 0, Time elapsed: 0.879 sec <<< FAILURE! - in com.noyaxe.nso.service.DeviceServiceTest
testInitializedForBindedSpaceForceBind(com.noyaxe.nso.service.DeviceServiceTest) Time elapsed: 0.309 sec <<< ERROR!
java.sql.SQLIntegrityConstraintViolationException: integrity constraint violation: foreign key no action; FK_L6IDVK78B2TLU8NO6EDJ0G6U8 table: CUSTOM_TABLE_COLUMN_SPACE_TYPE
at org.hsqldb.jdbc.Util.sqlException(Unknown Source)
at org.hsqldb.jdbc.Util.sqlException(Unknown Source)
at org.hsqldb.jdbc.JDBCStatement.fetchResult(Unknown Source)
……
……
Caused by: org.hsqldb.HsqlException: integrity constraint violation: foreign key no action; FK_L6IDVK78B2TLU8NO6EDJ0G6U8 table: CUSTOM_TABLE_COLUMN_SPACE_TYPE
at org.hsqldb.error.Error.error(Unknown Source)
at org.hsqldb.StatementDML.performReferentialActions(Unknown Source)
at org.hsqldb.StatementDML.delete(Unknown Source)
at org.hsqldb.StatementDML.executeDeleteStatement(Unknown Source)
at org.hsqldb.StatementDML.getResult(Unknown Source)
at org.hsqldb.StatementDMQL.execute(Unknown Source)
at org.hsqldb.Session.executeCompiledStatement(Unknown Source)
at org.hsqldb.Session.executeDirectStatement(Unknown Source)
at org.hsqldb.Session.execute(Unknown Source)
at org.hsqldb.jdbc.JDBCStatement.fetchResult(Unknown Source)
……
看异常信息,应该是删除记录时,外键级联导致的问题。在实体类里改变级联设置并不起作用。最后在StackOverflow上找了一个解决方法:编写一个类,继承AbstractTestExecutionListener,在beforeTestClass中取消级联依赖。具体如下:
import org.dbunit.database.DatabaseDataSourceConnection;
import org.dbunit.database.IDatabaseConnection;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
import javax.sql.DataSource;
public class ForeignKeyDisabling extends AbstractTestExecutionListener {
@Override
public void beforeTestClass(TestContext testContext) throws Exception {
IDatabaseConnection dbConn = new DatabaseDataSourceConnection(
testContext.getApplicationContext().getBean(DataSource.class)
);
dbConn.getConnection().prepareStatement("SET DATABASE REFERENTIAL INTEGRITY FALSE").execute();
}
}
把这个新的Listener添加测试类的注解中:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-test.xml")
@TestExecutionListeners({
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionDbUnitTestExecutionListener.class,
ForeignKeyDisabling.class})
参考:
http://stackoverflow.com/questions/2685274/tdd-with-hsqldb-removing-foreign-keys
三、PROPERTY_DATATYPE_FACTORY引起的警告
在jenkins中构建时,总是可以看到如下的警告信息:
WARN getDataTypeFactory, Potential problem found: The configured data type factory 'class org.dbunit.dataset.datatype.DefaultDataTypeFactory' might cause problems with the current database 'HSQL Database Engine' (e.g. some datatypes may not be supported properly). In rare cases you might see this message because the list of supported database products is incomplete (list=[derby]). If so please request a java-class update via the forums.If you are using your own IDataTypeFactory extending DefaultDataTypeFactory, ensure that you override getValidDbProducts() to specify the supported database products.
意思很好理解,就说默认的DataTypeFactory可能会引起问题,建议设置该属性值。解决方法也很明显:就是设置数据库连接的PROPERTY_DATATYPE_FACTORY属性的值。尝试了用Before、BeforeClass或者自定义ExecutionListener中都无法实现对该属性的设置。
那就只能先找到抛出这个异常的位置,然后向前推,逐步找到获取连接的地方。最后发现,连接是在DbUnitTestExecutionListener.prepareDatabaseConnection中获取连接,并且没有做什么进一步的处理,所以前面的设置都不起作用。看来又只能通过重写源代码来达成目的了。
直接上源码吧:
CustomTransactionDbUnitTestExecutionListener类: 完全复制DbUnitTestExecutionListener,只是增加一句代码。注意该类的包路径和DbUnitTestExecutionListener一致。
private void prepareDatabaseConnection(TestContext testContext, String databaseConnectionBeanName) throws Exception {
Object databaseConnection = testContext.getApplicationContext().getBean(databaseConnectionBeanName);
if (databaseConnection instanceof DataSource) {
databaseConnection = DatabaseDataSourceConnectionFactoryBean.newConnection((DataSource) databaseConnection);
}
Assert.isInstanceOf(IDatabaseConnection.class, databaseConnection);
((IDatabaseConnection)databaseConnection).getConfig().setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new HsqldbDataTypeFactory());
testContext.setAttribute(CONNECTION_ATTRIBUTE, databaseConnection);
}
绿色就是真正发挥作用的代码。
可是这个类并不能直接饮用,而是通过TransactionDbUnitTestExecutionListener的CHAIN被调用的,而TransactionDbUnitTestExecutionListener同样无法更改,同样只能建一个自定义的TransactionDbUnitTestExecutionListener类,CustomTransactionDbUnitTestExecutionListener:
public class CustomTransactionDbUnitTestExecutionListener extends TestExecutionListenerChain {
private static final Class<?>[] CHAIN = { TransactionalTestExecutionListener.class,
CustomDbUnitTestExecutionListener.class };
@Override
protected Class<?>[] getChain() {
return CHAIN;
}
}
那么测试类的注解也要修改:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-test.xml")
@TestExecutionListeners({
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
CustomTransactionDbUnitTestExecutionListener.class,
ForeignKeyDisabling.class})
四、@Transactional标签引起的问题
按照spring-dbunit-test的文档中说法,可以使用@Transactional确保数据的清洁。使用简单,只需要将上面的注解增加一个@Transactional,
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-test.xml")
@Transactional
@TestExecutionListeners({
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
CustomTransactionDbUnitTestExecutionListener.class,
ForeignKeyDisabling.class})
可是运行时,却出现了异常:
org.springframework.transaction.TransactionSystemException: Could not roll back JPA transaction; nested exception is javax.persistence.PersistenceException: unexpected error when rollbacking
at org.springframework.orm.jpa.JpaTransactionManager.doRollback(JpaTransactionManager.java:544)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:846)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.rollback(AbstractPlatformTransactionManager.java:823)
at org.springframework.test.context.transaction.TransactionalTestExecutionListener$TransactionContext.endTransaction(TransactionalTestExecutionListener.java:588)
at org.springframework.test.context.transaction.TransactionalTestExecutionListener.endTransaction(TransactionalTestExecutionListener.java:297)
at org.springframework.test.context.transaction.TransactionalTestExecutionListener.afterTestMethod(TransactionalTestExecutionListener.java:192)
……
Caused by: javax.persistence.PersistenceException: unexpected error when rollbacking
at org.hibernate.ejb.TransactionImpl.rollback(TransactionImpl.java:109)
at org.springframework.orm.jpa.JpaTransactionManager.doRollback(JpaTransactionManager.java:540)
... 32 more
Caused by: org.hibernate.TransactionException: rollback failed
at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.rollback(AbstractTransactionImpl.java:215)
at org.hibernate.ejb.TransactionImpl.rollback(TransactionImpl.java:106)
... 33 more
Caused by: org.hibernate.TransactionException: unable to rollback against JDBC connection
at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.doRollback(JdbcTransaction.java:167)
at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.rollback(AbstractTransactionImpl.java:209)
... 34 more
Caused by: java.sql.SQLNonTransientConnectionException: connection exception: connection does not exist
at org.hsqldb.jdbc.Util.sqlException(Unknown Source)
at org.hsqldb.jdbc.Util.sqlException(Unknown Source)
……
... 35 more
Caused by: org.hsqldb.HsqlException: connection exception: connection does not exist
at org.hsqldb.error.Error.error(Unknown Source)
at org.hsqldb.error.Error.error(Unknown Source)
... 40 more
最后通过查看源代码发现,CustomDbUnitTestExecutionListener会先于TransactionalTestExecutionListener执行,而前者在执行完毕就关闭了数据库连接,后者在回滚时,就发生了连接不存在的异常。
解决方法很简单,修改CustomTransactionalDbUnitTestExecutionListener:
private static final Class<?>[] CHAIN = {CustomDbUnitTestExecutionListener.class, TransactionalTestExecutionListener.class};
也就是数组两个元素调换下位置。
分享到:
相关推荐
翻看之前的文章才发现,最近一次记录持续集成竟然是3年前,并且...说起来可笑,从3年前第一次准备做持续集成式,就开始考虑测试数据访问层的一些问题: 难道我要在测试服务器上装一个MySQL? 数据库结构发生了变化怎
第4次-3(第5章 单元测试和集成测试——集成测试第4次-3(第5章 单元测试和集成测试——集成测试第4次-3(第5章 单元测试和集成测试——集成测试第4次-3(第5章 单元测试和集成测试——集成测试第4次-3(第5章 单元...
集成测试指南——指导集成测试全过程,集成测试实战,每一步都很详尽
Flex持续集成之单元测试
持续集成与单元测试xmzy.pdf
开发质量低下如何解决,如何确保核心模块质量?
⼤数据整理 ⼤数据整理——数据集成 数据集成 数据集成 数据集成 1.背景: 背景: 因业务需要,事业单位内部普遍构建了多个异构的信息系统,这些信息系统中管理的数据源彼此独⽴、相互封闭,形成"信息孤岛"⽆法形成 ...
软件测试分为单元测试,集成测试等,其中单元测试具体操作如下文档。
在V模型开发中,Tessy主要应用在单元测试和集成测试阶段。单元测试通过运行代码检测出函数中错误,比如算法错误、接口问题等;集成测试则在单元测试的基础上验证单元之间接口的正确性。基于越早发现bug开发成本越低...
系统集成的模式与方法: 功能测试 系统测试 压力测试、容量测试和性能测试 安全性、可靠性和容错性测试
手把手教你jenkins+python+allure持续集成(csdn)————程序
持续集成与自动化测试持续集成与自动化测试持续集成与自动化测试持续集成与自动化测试持续集成与自动化测试持续集成与自动化测试持续集成与自动化测试持续集成与自动化测试持续集成与自动化测试持续集成与自动化测试
持续集成技术与单元测试方法 内容包括以下: 持续集成技术 – 持续集成的基本概念 – 持续集成的作用和优点 – 如何实施持续集成 • 单元测试技术 – 单元测试基本方法 – 单元测试实践中常见的问题 – 单元...
软件测试——测试计划 该文档适用于集成测试、系统测试、验收测试的计划制订,并不适用于单元测试计划。
集成测试的概念 集成测试的主要内容和方法 集成测试的过程 经验介绍
经过近一个月的研究,完成了对maven、git、jenkins、tomcat的集成,超详细的搭建步骤,适合新人研究参考
详细描述了持续集成环境的搭建步骤,包括集成hudson sonar maven等环境进行单元测试自动执行,代码规则校验等。是搭建持续集成环境,改建软件自动化测试和校验的学习精品。
模拟CMOS集成电路设计——拉扎维,学模拟集成电路经典教程
支付宝集成过程详解——运行DEMO