持续集成之路——数据访问层单元测试遇到的问题

简介: 在编写数据访问层的单元测试时,遇到不少问题,有些问题可以很容易Google到解决方法,而有些只能自己研究解决。这里分享几个典型的问题以及解决方法。先交代一下用到的测试框架 Spring Test + SpringTestDbUnit + DbUnit。

在编写数据访问层的单元测试时,遇到不少问题,有些问题可以很容易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月前
|
SQL 人工智能 JSON
Flink 2.1 SQL:解锁实时数据与AI集成,实现可扩展流处理
简介:本文整理自阿里云高级技术专家李麟在Flink Forward Asia 2025新加坡站的分享,介绍了Flink 2.1 SQL在实时数据处理与AI融合方面的关键进展,包括AI函数集成、Join优化及未来发展方向,助力构建高效实时AI管道。
651 43
|
3月前
|
SQL 人工智能 JSON
Flink 2.1 SQL:解锁实时数据与AI集成,实现可扩展流处理
本文整理自阿里云的高级技术专家、Apache Flink PMC 成员李麟老师在 Flink Forward Asia 2025 新加坡[1]站 —— 实时 AI 专场中的分享。将带来关于 Flink 2.1 版本中 SQL 在实时数据处理和 AI 方面进展的话题。
230 0
Flink 2.1 SQL:解锁实时数据与AI集成,实现可扩展流处理
|
3月前
|
SQL 关系型数据库 Apache
从 Flink 到 Doris 的实时数据写入实践 —— 基于 Flink CDC 构建更实时高效的数据集成链路
本文将深入解析 Flink-Doris-Connector 三大典型场景中的设计与实现,并结合 Flink CDC 详细介绍了整库同步的解决方案,助力构建更加高效、稳定的实时数据处理体系。
1407 0
从 Flink 到 Doris 的实时数据写入实践 —— 基于 Flink CDC 构建更实时高效的数据集成链路
|
3月前
|
机器学习/深度学习 SQL 大数据
什么是数据集成?和数据融合有什么区别?
在大数据领域,“数据集成”与“数据融合”常被混淆。数据集成关注数据的物理集中,解决“数据从哪来”的问题;数据融合则侧重逻辑协同,解决“数据怎么用”的问题。两者相辅相成,集成是基础,融合是价值提升的关键。理解其差异,有助于企业释放数据潜力,避免“数据堆积”或“盲目融合”的误区,实现数据从成本到生产力的转变。
什么是数据集成?和数据融合有什么区别?
|
9月前
|
存储 人工智能 测试技术
小鱼深度评测 | 通义灵码2.0,不仅可跨语言编码,自动生成单元测试,更炸裂的是集成DeepSeek模型且免费使用,太炸裂了。
小鱼深度评测 | 通义灵码2.0,不仅可跨语言编码,自动生成单元测试,更炸裂的是集成DeepSeek模型且免费使用,太炸裂了。
141490 29
小鱼深度评测 | 通义灵码2.0,不仅可跨语言编码,自动生成单元测试,更炸裂的是集成DeepSeek模型且免费使用,太炸裂了。
|
10月前
|
容灾 安全 关系型数据库
数据传输服务DTS:敏捷弹性构建企业数据容灾和集成
数据传输服务DTS提供全球覆盖、企业级跨境数据传输和智能化服务,助力企业敏捷构建数据容灾与集成。DTS支持35种数据源,实现全球化数据托管与安全传输,帮助企业快速出海并高效运营。瑶池数据库的全球容灾、多活及集成方案,结合DTS的Serverless和Insight功能,大幅提升数据传输效率与智能管理水平。特邀客户稿定分享了使用DTS加速全球业务布局的成功经验,展示DTS在数据分发、容灾多活等方面的优势。
227 0
|
5月前
|
运维 安全 数据管理
Dataphin V5.1 企业级发布:全球数据无缝集成,指标管理全新升级!
企业数据管理难题?Dataphin 5.1版来解决!聚焦跨云数据、研发效率、指标管理和平台运维四大场景,助力数据团队轻松应对挑战。无论是统一指标标准、快速定位问题,还是提升管理安全性,Dataphin都能提供强大支持。3分钟了解新版本亮点,让数据治理更高效!
|
10月前
|
人工智能 安全 Dubbo
Spring AI 智能体通过 MCP 集成本地文件数据
MCP 作为一款开放协议,直接规范了应用程序如何向 LLM 提供上下文。MCP 就像是面向 AI 应用程序的 USB-C 端口,正如 USB-C 提供了一种将设备连接到各种外围设备和配件的标准化方式一样,MCP 提供了一个将 AI 模型连接到不同数据源和工具的标准化方法。
4118 111
|
9月前
|
Java 关系型数据库 MySQL
SpringBoot 通过集成 Flink CDC 来实时追踪 MySql 数据变动
通过详细的步骤和示例代码,您可以在 SpringBoot 项目中成功集成 Flink CDC,并实时追踪 MySQL 数据库的变动。
2096 45
|
10月前
|
机器学习/深度学习 PyTorch 测试技术
LossVal:一种集成于损失函数的高效数据价值评估方法
LossVal是一种创新的机器学习方法,通过在损失函数中引入实例级权重,直接在训练过程中评估数据点的重要性,避免了传统方法中反复重训练模型的高计算成本。该方法适用于回归和分类任务,利用最优传输距离优化权重,确保模型更多地从高质量数据中学习。实验表明,LossVal在噪声样本检测和高价值数据点移除等任务上表现优异,具有更低的时间复杂度和更稳定的性能。论文及代码已开源,为数据价值评估提供了高效的新途径。
209 13
LossVal:一种集成于损失函数的高效数据价值评估方法

热门文章

最新文章

下一篇
开通oss服务