`

解决unitils中的dbunit使用spring中定义的多数据源

    博客分类:
  • Test
阅读更多
最近在研究unitils, dbunit来适应目前的单元测试.
在unitils中将要使用的数据源都定义在unitils.properties中, 而在我们的测试配置中, 同样的数据源也在spring中配置了一份儿, 因此本人希望同样的配置不应该出现在两个方面, 从而增加维护成本. 另一方面还可以通过spring来解决多数据源的问题. 于是开始看unitils的源代码, 原来在dbunit module中, 获取数据源的代码:
public DbUnitDatabaseConnection getDbUnitDatabaseConnection(String schemaName) {
        DbUnitDatabaseConnection dbUnitDatabaseConnection = dbUnitDatabaseConnections.get(schemaName);
        if (dbUnitDatabaseConnection == null) {
            dbUnitDatabaseConnection = createDbUnitConnection(schemaName);
            dbUnitDatabaseConnections.put(schemaName, dbUnitDatabaseConnection);
        }
        return dbUnitDatabaseConnection;
    }
    protected DbUnitDatabaseConnection createDbUnitConnection(String schemaName) {
        // A DbSupport instance is fetched in order to get the schema name in correct case
        DataSource dataSource = getDatabaseModule().getDataSourceAndActivateTransactionIfNeeded();
        ...
    }


也就是说, 最终的数据源是通过DatabaseModule来获取的, 因此我们需要对这些代码进行改造, 但是还有另外一个问题, unitils的spring module对所有测试的spring context都是按照以测试类为key, context为value进行缓存的. 因此要获取spring的context, 必须知道当前的测试类, 因此这里需要整体调整Dbunit module的insertDataset()方法的内部方法调用的参数. 在处理流程中增加testObject参数. 这个需要修改的代码非常多, 这里不一一展开, 对于从spring中获取数据源的代码如下:
    private DataSource getDataSource(String schemaName, Object testObject) {
        // 从spring中取
        SpringModule springModule = Unitils.getInstance().getModulesRepository().getModuleOfType(SpringModule.class);
        DataSource dataSource = (DataSource) springModule.getSpringBean(testObject, schemaName);
        if (dataSource == null) {
            throw new RuntimeException(String.format("datasource[%s]在spring配置文件中不存在", schemaName));
        }
        return dataSource;
    }


为了将spring的datasource与dbunit能够关联起来, 本人对构造的测试数据做了一个约定, 对于操作的数据表, 必须加数据源标识前缀, 而数据源标识则对应spring bean配置文件中的id, 比如配置了一个datasource bean:
	<bean id="db1" parent="parentDataSource">
		<property name="url">
			<value>jdbc:oracle:oci:@${db1.name}</value>
		</property>
		<property name="username">
			<value>${db1.username}</value>
		</property>
		<property name="password">
			<value>${db1.password}</value>
		</property>
	</bean>

如果有一个dataset中有一个table(table1)跟该datasource关系, 那么该表名必须这样定义: db1.table1. 也可以将db1前缀理解为schema(但这里实际并不是).对于这个问题, 已经跟unitils的founder进行了交流, 并在jira中增加了一个issue: http://jira.unitils.org/browse/UNI-190 在未来版本中将实现该功能.

在对数据清理的处理上的改进
对于构造的测试准备数据的清理, dbunit在默认情况下, 假定所有测试的数据库表必须建立主键, 否则会抛出异常, 因为在清理数据的时候需要利用主键以及构造的对应主键值来做db的delete操作.而我们目前存在一些关联表是没有主键的, 因此给使用unitils带来了一定的麻烦. 但也是可以搞定的. 另外一个问题就是, 对于所有的构造数据都会根据dataset中定义的table来处理, 而目前我们面临的另外一个问题, 就是在测试前, 希望能通过指定的sql来对测试环境对某些数据进行清理操作, 以避免对测试造成影响. 于是对unitils的DataSetLoadStrategy和dbunit的DatabaseOperation进行了扩展.
本人定义了一个ExecuteSqlOperation用来执行指定的sql语句:
public class ExecuteSqlOperation extends DatabaseOperation {
    private Map<String, List<String>> sqlMap;
    private static final Logger logger = LoggerFactory.getLogger(ExecuteSqlOperation.class);

    public ExecuteSqlOperation(Map<String, List<String>> sqlMap) {
        this.sqlMap = sqlMap;
    }

    @Override
    public void execute(IDatabaseConnection connection, IDataSet dataSet) throws DatabaseUnitException, SQLException {
        logger.debug("execute(connection={}, dataSet={}) - start", connection, dataSet);

        DatabaseConfig databaseConfig = connection.getConfig();
        IStatementFactory factory =
                (IStatementFactory) databaseConfig.getProperty(DatabaseConfig.PROPERTY_STATEMENT_FACTORY);

        // for each table
        Iterator<Entry<String, List<String>>> iterator = sqlMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Entry<String, List<String>> sqlEntry = iterator.next();
            String schema = sqlEntry.getKey();

            // Do not process empty table
            if (StringUtils.isBlank(schema) || sqlEntry.getValue().isEmpty()) {
                logger.warn(String.format("schema{%s} or sqls(%s) is empty", schema, sqlEntry.getValue()));
                continue;
            }

            // don't find the datasource
            if (!connection.getSchema().equalsIgnoreCase(schema)) {
                continue;
            }

            IPreparedBatchStatement statement = null;

            try {
                for (String sql : sqlEntry.getValue()) {
                    statement = factory.createPreparedBatchStatement(sql, connection);
                    statement.addBatch();
                }

                statement.executeBatch();
                statement.clearBatch();

                // clear schema and sql
                iterator.remove();
            } finally {
                if (statement != null) {
                    statement.close();
                }
            }
        }
    }

    public void setSqlMap(Map<String, List<String>> sqlMap) {
        this.sqlMap = sqlMap;
    }
}


从代码中我们可以看出, dbunit中的DatabaseOperation 与dataset是有非常强的耦合的(本来嘛, dataset是整个dbunit的模型核心), 而这里我们对dataset是不需要的: 拿到connection, 执行sql, 退出.
然后定义了一个CustemAndInsertDataSetLoadStrategy, 用来引入我们上面定义的这个ExecuteSqlOperation :
public abstract class CustemAndInsertDataSetLoadStrategy implements DataSetLoadStrategy {
    public void execute(DbUnitDatabaseConnection dbUnitDatabaseConnection, IDataSet dataSet) {
        try {
            doExecute(dbUnitDatabaseConnection, dataSet);
            DatabaseOperation.INSERT.execute(dbUnitDatabaseConnection, dataSet);
        } catch (DatabaseUnitException e) {
            throw new UnitilsException("Error while executing DataSetLoadStrategy", e);
        } catch (SQLException e) {
            throw new UnitilsException("Error while executing DataSetLoadStrategy", e);
        }
    }

    abstract protected void doExecute(DbUnitDatabaseConnection dbUnitDatabaseConnection, IDataSet dataSet)
            throws DatabaseUnitException, SQLException;
}


在实际测试中使用:
@DataSet(loadStrategy = MyTest.MyDataSetLoadStrategy.class)
public class MyTest extends IcBaseCase2 {
    public static class MyDataSetLoadStrategy extends CustemAndInsertDataSetLoadStrategy {
        private Map<String, List<String>> sqlMap = new HashMap<String, List<String>>();
        private ExecuteSqlOperation executeSqlOperation;
        public SpuRelationDataSetLoadStrategy() {
            sqlMap.put("db1", Collections
                    .singletonList("delete table1 where id = 110"));
            executeSqlOperation = new ExecuteSqlOperation(sqlMap);
        }

        @Override
        protected void doExecute(DbUnitDatabaseConnection dbUnitDatabaseConnection, IDataSet dataSet)
                throws DatabaseUnitException, SQLException {
            executeSqlOperation.execute(dbUnitDatabaseConnection, dataSet);
        }
    }


当然中间省略了一些实现细节, 发现经过这样扩展之后, 基本能满足我们的测试需要.
感觉多数据源的使用是一个比较少的场景, 从dbunit, unitils的feature中就可以看出来, 貌似二者基本上没有做实现, 因此有了上面的代码.
0
0
分享到:
评论

相关推荐

    unitils整合dbunit利用excel进行单元测试

    unitils整合dbunit利用excel进行单元测试 包含mock以及整合spring进行测试

    主题:在Spring中结合Dbunit对Dao进行集成单元测试

    下面将详细介绍如何在Spring中使用Dbunit进行Dao的集成单元测试。 首先,我们需要理解集成测试的概念。集成测试是在所有模块单独通过单元测试后,将它们组合在一起进行的测试,目的是检查模块间的交互是否正确。在...

    使用Unitils测试DAO

    标题“使用Unitils测试DAO”涉及的是在Java开发中如何利用Unitils库来高效地测试数据访问对象(DAO)层的代码。Unitils是一个强大的、易于使用的集成测试框架,它简化了诸如数据库、ORM(对象关系映射)框架如...

    spring与dbunit集成测试

    2. **引入DBUnit依赖**:在项目的`pom.xml`或`build.gradle`文件中添加DBUnit的依赖,这样就可以在测试类中使用DBUnit的功能。 3. **设置数据源**:在Spring配置文件中定义一个数据源,用于连接测试数据库。确保该...

    Unitils单元测试

    对于依赖注入,Unitils能够轻松地将Spring管理的Bean注入到单元测试中,允许在测试中使用Spring容器中的SessionFactory。它也简化了Mock对象的创建,特别是在使用EasyMock时,通过反射参数匹配使得Mock对象的使用...

    DBUNIT使用

    用户可以在自己的测试类中直接或间接调用 DbUnit,或者从 Ant 中使用 DbUnit 来执行某些任务。 DbUnit 的优点是提供了一种相对简单灵活的方式来准备和验证数据库,这种方式独立于被测代码。 DbUnit 还可以与 JUnit ...

    dbunit-spring-demo:DBUnit Utility演示(使用Spring)

    【dbunit-spring-demo】是一个基于Java的项目,它展示了如何在Spring框架中有效地使用DBUnit工具进行数据库测试。DBUnit是JUnit的一个扩展,专门用于数据库的集成测试,它允许开发者在测试之前填充数据库,执行测试...

    Unitils示例

    通过集成 DBUnit,Unitils 可以帮助我们在测试中管理和操作数据库数据,确保每个测试用例都在干净的环境中运行。`pom.xml` 文件中的依赖可能包含了 DBUnit 的配置,使得我们可以在测试中导入和导出数据,进行数据...

    如何使用DBUnit做数据备份恢复

    在提供的文件列表中,我们可以看到 student_mysql.sql(可能是 SQL 导出的数据库备份),以及几种不同类型的 XML 文件(student_nonflat.xml、student_exp.xml、student_flat.xml、student_pre.xml),这些都是 ...

    dbunit使用实例

    这个例子展示了如何在测试类中设置数据库连接,加载预期数据,然后在测试方法中使用 `CLEAN_INSERT` 操作清理并插入数据,最后验证数据库中的数据是否与预期相符。 ### 常见问题 1. **数据集格式错误**: 确保数据...

    DBUnit最佳实践之数据备份与恢复

    这些示例可能包括如何配置DBUnit,定义数据格式,以及如何在测试前后触发数据操作的代码片段。通过阅读和理解这些代码,开发者可以更好地掌握DBUnit的实际应用。 总的来说,DBUnit是数据库测试领域的一个强大工具,...

    dbunit使用必需Jar包

    1. **准备数据**:使用 DBUnit 导入 XML 文件或 Excel 表格中的测试数据到数据库。 2. **执行测试**:在测试方法中,调用 DBUnit 的 API 运行测试,比如使用 `IDatabaseConnection` 和 `IDataSet` 接口。 3. **验证...

    Spring Boot 与DBunit 配合使用方法

    首先,为了在 Spring Boot 项目中使用 DBUnit,我们需要添加 DBUnit 的依赖。这可以通过在 `build.gradle` 或 `pom.xml` 文件中引入相应的坐标来完成。例如,在 Gradle 中,可以添加如下依赖: ```groovy ...

    dbunit-2.4.9 源码

    DBUnit 是一个开源的 Java 库,专门用于数据库测试,它是 xUnit 测试框架的一部分,提供了数据驱动测试的解决方案。在版本 2.4.9 中,DBUnit 提供了一系列的功能,帮助开发者在进行单元测试时能够管理和操作数据库的...

    介绍dbunit的使用和原理,核心组件介绍

    DbUnit提供了两种实现方式:`DatabaseConnection` 包装了JDBC连接,而 `DatabaseDataSourceConnection` 则包装了JDBC的数据源。这两个类使得DbUnit可以操作数据库并执行相关的数据操作。 **IDataSet** 接口表示一个...

    dbunit开发文档

    2. Spring 集成:如果你的项目使用 Spring,可以利用 Spring's `DbUnitTestExecutionListener` 来自动管理 DBUnit 的生命周期。 3. Maven 插件:也有 Maven 插件可用来自动化 DBUnit 测试,例如 maven-dbunit-...

    Dbunit数据库连接下载

    src 文件夹通常包含源代码,尽管在这个压缩包中没有详细列出其内容,但在实际的Dbunit项目中,src文件夹下会有Java源代码、测试代码以及相关的配置文件。 doc 文件夹可能包含了Dbunit的API文档或者其他技术文档,...

    dbunit-2.4.7所有jar以及dbunit入门教程

    数据集是 DBUnit 的基础,它通常是一个 XML 文件或者 CSV 文件,描述了测试用例中的预期数据库状态。这些文件包含了表的行和列,模拟了数据库中的数据。操作模式则定义了如何将数据集加载到数据库,比如 `CLEAN_...

    DBUnit使用文档

    - 在这里,`dbunit` 任务同样指定数据库连接属性,但操作类型为 `export`,将数据导出到 `data.xml` 文件中。 - `&lt;query&gt;` 和 `&lt;table&gt;` 标签用于指定要导出的特定查询和表。 3. **操作类型(Operation Types)**...

Global site tag (gtag.js) - Google Analytics