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

在编写数据访问层的单元测试时,遇到不少问题,有些问题可以很容易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};

也就是数组两个元素调换下位置。

时间: 2025-01-30 18:02:54

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

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

        翻看之前的文章才发现,最近一次记录持续集成竟然是3年前,并且只记录了两篇,实在是惭愧.不过,持续集成的这团火焰却始终在心中燃烧,希望这次的开始可以有些突破.          测试是持续集成的基石,没有测试的集成基本上是毫无意义的.如何写好测试就是横亘在我面前的第一个问题.那就从数据访问层开始吧.说起来可笑,从3年前第一次准备做持续集成式,就开始考虑测试数据访问层的一些问题: 难道我要在测试服务器上装一个MySQL? 数据库结构发生了变化怎么办? 怎么样才能消除测试间的依赖? 测

持续集成之路——数据访问层的单元测试(续)

        在上一篇中,完成了对测试用数据源的配置.下面继续构建可运行的测试.        三.使用DBUnit管理数据         测试的维护一直是我比较头疼的问题,期望可以有一个比较易于维护和可复用的方法来管理这些数据.在没有更好的方法之前,暂时选用DBUnit.(反思:其实我一直在为没有发生的事情担心,使得事情根本没有进展.从已存在的.最简单的地方入手,才是正确的处理方式.)         在pom.xml中引入dbunit和springtestdbunit包,后者提供通过注解

持续集成之路—服务层的单元测试

在完成了数据访问层的单元之后,接下来看如何编写服务层(Service)的单元测试.服务层应该是整个系统中得重中之重,严密的业务逻辑设计保证了系统稳定运行,所以这一层的单元测试也应该占很大比重.虽然一般情况下单元测试应该尽量通过mock剥离依赖,但是由于在当前的项目中数据访问层使用spring-data框架,并没有包含太多的逻辑,因此我就把服务层和数据访问层放在做了一个伪单元测试. 一.一般逻辑的单元测试. 这里采用的方式和数据访问层几乎是一样的,主要包含三步: 1. 通过@DatabaseSet

持续集成之路——服务层的单元测试

        在完成了数据访问层的单元之后,接下来看如何编写服务层(Service)的单元测试.服务层应该是整个系统中得重中之重,严密的业务逻辑设计保证了系统稳定运行,所以这一层的单元测试也应该占很大比重.虽然一般情况下单元测试应该尽量通过mock剥离依赖,但是由于在当前的项目中数据访问层使用spring-data框架,并没有包含太多的逻辑,因此我就把服务层和数据访问层放在做了一个伪单元测试.         一.一般逻辑的单元测试.         这里采用的方式和数据访问层几乎是一样的,主

求助!SSH2,action调用数据访问层,插入数据不报错也不插入,查询值全部返回true??

问题描述 如题,action调用数据访问层,插入数据时不报错也不能插入,查询方法值全部返回true.另,service层和dao层均通过单元测试,可以正常运行.操作.但是集成action和jsp就会出问题.使用了<propertyname="connection.autocommit">true</property>.无效.上代码:applicationcontext.xml<?xmlversion="1.0"encoding=&quo

如何破局CI工具拉锯战:探寻中小企业的持续集成之路

摘要:随着持续集成技术的不断成熟,各种持续集成相关的开源和商用软件层出不穷,但是对于中小型企业的技术团队而言,往往在进行持续集成实践时会陷入工具之间的拉锯战.那么,对于中小团队而言,如何才能实现高效敏捷的持续集成方案?2017苏州云栖大会云效专场上,南京路特CTO.阿里云MVP戚俊结合实践经验为大家分享了中小团队持续集成之路. 以下内容根据演讲视频以及PPT整理而成. 虽然持续集成的概念已经火了很长时间了,但是因为各个企业的规模以及业务类型都不同,所以在持续集成中遇到的问题也各不相同.本次将结合

统一身份认证子系统数据库设计与数据访问层实现

访问|设计|数据|数据库|数据库设计 目 录 一 引言--------------------------------1 二 需求分析 (一)系统的功能要求------------------------2 (二)系统的性能要求------------------------2 (三)运行环境要求-------------------------2 (四)开发工具简介-------------------------2 三 总体设计 (一)系统模块化分----------------------

实战 .Net 数据访问层 - 1

访问|数据 实战 .Net 数据访问层 l 特别说明 本篇实战共分23段,非作者有意如此,乃受CSDN发表文章之64K所限. 虽然有几段根本没有达到64K,但估计是HTML Source超过了这个范 围,所以也不得不单独分段(大都是源代码),请大家谅解. 如果有朋友需要完整文档,请发邮件给我: mailto:xuefeng.zhang@bearingpoint.com l 引言 这次的讨论是上一部分"剖析 .Net 下的数据访问层技术"的一个续,但也可独立成章,为突出主题,作者就特意换

Spring.Net+NHibenate+Asp.Net mvc +ExtJs 系列 3 ----数据访问层

在上一篇中,我们已经搭建起了整个解决方案的项目,并且建好了数据库,完成了实体类和Nhibernate映射文件.在本文中,将定义数据访问接口,并利用Nhibernate实现接口,利用Spring.net配置起来dao.并对其进行单元测试. 数据访问层也和Petshop等框架一样,分为数据访问的接口以及实现,不过这里的数据访问实现相比之下就清晰和明显了的多,Nhibernate本身就是支持多数据库的,所以这样做不是为了多数据库,而是为了Nhibernate的可插拨,即使哪一天发现由于一些问题,比如说