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

简介: 在编写数据访问层的单元测试时,遇到不少问题,有些问题可以很容易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};
也就是数组两个元素调换下位置。


目录
相关文章
|
1天前
|
传感器 数据采集 算法
LabVIEW无人机大气数据智能测试系统
LabVIEW无人机大气数据智能测试系统
10 3
|
2天前
|
Web App开发 测试技术 C++
Playwright安装与Python集成:探索跨浏览器测试的奇妙世界
Playwright是新兴的跨浏览器测试工具,相比Selenium,它支持Chrome、Firefox、WebKit,执行速度快,选择器更稳定。安装Playwright只需一条`pip install playwright`的命令,随后的`playwright install`会自动添加浏览器,无需处理浏览器驱动问题。这一优势免去了Selenium中匹配驱动的烦恼。文章适合寻求高效自动化测试解决方案的开发者。
10 2
|
4天前
|
机器学习/深度学习 敏捷开发 监控
深入探索软件测试中的持续集成与持续部署(CI/CD)实践
【5月更文挑战第10天】 在现代软件开发周期中,"持续集成"(CI)与"持续部署"(CD)是提升效率、确保质量的重要环节。本文将详细探讨CI/CD在软件测试中的应用,包括其基本概念、实施策略、工具应用及面临的挑战。不同于一般性概述,本文将重点分析如何优化测试流程以适应CI/CD环境,并提出针对性的改进措施。通过实际案例分析,揭示成功实施CI/CD的最佳实践,并讨论如何在不断变化的技术环境中保持测试策略的前瞻性和灵活性。
|
6天前
|
JSON 测试技术 数据格式
Elasticsearch 8.X 如何生成 TB 级的测试数据 ?
Elasticsearch 8.X 如何生成 TB 级的测试数据 ?
13 0
|
7天前
|
敏捷开发 监控 jenkins
探索自动化测试在持续集成环境中的关键作用
【5月更文挑战第6天】 在快速迭代的软件开发周期中,持续集成(CI)已经成为确保代码质量和加速交付的重要实践。本文将深入探讨自动化测试作为持续集成不可或缺的组成部分,它如何通过减少人为错误、提高测试覆盖率和加快反馈周期来强化软件开发流程。通过对现代自动化测试工具的评估以及真实案例的分析,我们揭示了自动化测试在提升软件可靠性和效率方面的核心价值,并提出了实现高效自动化测试策略的最佳实践。
|
7天前
|
敏捷开发 数据管理 测试技术
探索自动化测试在持续集成环境中的优化策略
【5月更文挑战第6天】 本文旨在深入剖析自动化测试在持续集成(CI)环境中所面临的挑战,并提出一系列创新的优化策略。通过对现代软件开发过程中自动化测试角色的分析,我们揭示了在快速迭代和部署的背景下,如何通过改进测试框架、选择合适的测试工具、以及实施数据驱动测试等手段来提高测试效率和准确性。文章不仅聚焦于技术层面的解决方案,还探讨了团队协作和流程管理对提升自动化测试效能的重要性。
|
11天前
|
设计模式 人工智能 测试技术
深入探究持续集成中的自动化测试策略
【5月更文挑战第3天】 在现代软件开发实践中,持续集成(CI)已成为提高开发效率、确保代码质量和加速产品上市速度的关键因素。自动化测试作为CI流程的核心组成部分,它确保了快速的反馈循环和高质量的构建。本文将探讨在持续集成环境中实施高效自动化测试的策略,包括测试用例的优化、测试环境的管理、以及如何整合最新的测试工具和技术。通过具体案例分析,我们将了解如何构建一个既灵活又健壮的自动化测试系统,以支持不断变化的软件开发需求。
|
11天前
|
敏捷开发 监控 测试技术
探索自动化测试在持续集成中的关键作用
【5月更文挑战第3天】 随着敏捷开发和持续集成(CI)实践的普及,自动化测试已成为确保软件质量和加速交付过程的核心环节。本文旨在深入探讨自动化测试在持续集成环境中的作用,重点分析其在提高测试效率、降低人力成本及维护软件质量保障体系中的重要性。通过实际案例研究,本文揭示了有效实施自动化测试策略的关键因素,并提出了一系列优化建议,以帮助软件开发团队充分利用自动化测试工具,实现持续交付的高效运作。
32 6
|
13天前
|
监控 JavaScript 前端开发
【TypeScript技术专栏】TypeScript的单元测试与集成测试
【4月更文挑战第30天】本文讨论了在TypeScript项目中实施单元测试和集成测试的重要性。单元测试专注于验证单个函数、类或模块的行为,而集成测试关注不同组件的协作。选用合适的测试框架(如Jest、Mocha),配置测试环境,编写测试用例,并利用模拟和存根进行隔离是关键。集成测试则涉及组件间的交互,需定义测试范围,设置测试数据并解决可能出现的集成问题。将这些测试整合到CI/CD流程中,能确保代码质量和快速响应变化。
|
14天前
|
分布式计算 DataWorks 数据库
DataWorks操作报错合集之DataWorks使用数据集成整库全增量同步oceanbase数据到odps的时候,遇到报错,该怎么处理
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
25 0