`

修改dbunit的几个bug兼对dbunit进行扩展

    博客分类:
  • Test
阅读更多
最近在对unitils进行扩展, 主要是数据库这块儿的内容, 对, 就是dbunit, dbunit给我的感觉是扩展性太差了, 很多类的构造函数采用包可见, 抽象类的抽象方法包可见, 根本没办法继承复写某些方法, 可定制性和unitils比起来也差的不是一点点, 根本就是一个封闭的系统. 导致很多代码不得不大段的拷贝代码来满足自身的需要.

我这里采用了excel格式来构造测试数据, 目前发现的dbunit(使用版本为2.4.6)的几个问题:
  • 有些大数字会采用科学计数法来表示, 导致在解析的时候数据不正确
  • dbunit内部对日期时间的处理会用到TimeZone这个东东, 这个在国际化方面应该是有价值的, 但是在我们的测试中却会导致时间与格林威治时间做8个小时的偏移转换
  • 对excel中的空行没有进行处理(比如excel本来只有两行数据, 但是不知什么原因会存在一些空行, 导致在插入数据库的时候会有Null相关的错误)
  • 对测试数据不仅涉及到根据主键清理, 有时候还需要根据一些特殊的字段值进行清理, 比如唯一键, 而这个需要进行扩展, 而XlsTable的包可见, 基本上必须另外实现一套(TMD,恨得让人直咬牙).


针对以上问题的解决方案:
科学计数法问题
这里需要对XlsTable中的getValue()方法下进行处理, 具体代码如下:
    static final Pattern pattern = Pattern.compile("[eE]");
    private BigDecimal toBigDecimal(double cellValue) {
        String resultString = String.valueOf(cellValue);

        // 针对科学计数法的处理(对于小数位数精确到5位)
        if (pattern.matcher(resultString).find()) {
            DecimalFormat format = new DecimalFormat("#####.#####");
            resultString = format.format(cellValue);
        }


        if (resultString.endsWith(".0")) {
            resultString = resultString.substring(0, resultString.length() - 2);
        }

        BigDecimal result = new BigDecimal(resultString);
        return result;

    }

这里暂定精确到小数5位, 有没有更好的解决方案?

日期时间问题
在某个地方对TimeZone做个初始化, 具体原理懒得去探究了, 反正我这样解决了问题
        // 由于dbunit对excel的时间处理会使用TimeZone.getOffset()做一个偏移转换, 这里需要设置一下
        TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));


空行的问题
这个需要实现一个IBatchStatement, 在批量处理数据的时候, 如果遇到空行应该跳过, dbunit有一个自己的实现:BatchStatement, 日!把构造函数声明为包可见, 让你无从继承改写. 无奈copy一些代码重写了一个. 另外还需要重写一个PreparedStatementFactory, 用来创建自己的那个IBatchStatement实现类.
public class TPreparedBatchStatement implements IPreparedBatchStatement {
    boolean notAllNull; // 所有参数是否为null
    boolean noParameter = true; // 是否指定参数
    private int _index;

    ...

    public void close() throws SQLException {
        ...
    }

    public void addValue(Object value, DataType dataType)
            throws TypeCastException, SQLException {
        logger.debug("addValue(value={}, dataType={}) - start", value, dataType);

        noParameter = false;

        // Special NULL handling
        if (value == null || value == ITable.NO_VALUE) {
            _statement.setNull(++_index, dataType.getSqlType());
            return;
        }

        notAllNull = true;

        dataType.setSqlValue(value, ++_index, _statement);
    }

    public void addBatch() throws SQLException {
        logger.debug("addBatch() - start");
        // 没有参数, 或者有参数, 但是参数不全为null
        if (noParameter || (!noParameter && notAllNull)) {
            _statement.addBatch();
            notAllNull = false;
            noParameter = true;
        }
        _index = 0;
    }
...

具体就是加了几个判断, 批量处理的行数据为null的时候不进行批量操作

其实dbunit也有一个类似unitils的控制中心:DatabaseConfig类. 但是可配置的东东太少太少, 我需要的没有, 有的我一个都不需要:(, 无语.

根据唯一键清数据
这个借鉴了dbunit原来的做法(现在去掉了唯一键标识的功能), 给字段加下划线style来标识. 本来打算根据数据库的metadata信息来获取唯一键的, 但是jdbc没有提供这样的接口, 而这种用下划线标识具有更好的扩展性, 可维护性, 可移植性.
具体做法是在XlsTable的createMetaData()方法中检查字段的style, 然后利用了Column中的remark属性来存储唯一键标识信息.
            Column column = null;
            // 标识唯一键
            byte underline = cell.getCellStyle().getFont(workbook).getUnderline();
            if (underline == 1) {
                column = new Column(columnName, DataType.UNKNOWN, null, null, null, "unique", null);
            } else {
                column = new Column(columnName, DataType.UNKNOWN);
            }
            columnList.add(column);


然后重新定义了一个DatabaseOperation:
public class DeleteByUniqueKeyOperation extends DatabaseOperation {
    private static final Logger logger = LoggerFactory.getLogger(DeleteByUniqueKeyOperation.class);

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

        IStatementFactory factory = getFactory(connection);


        // for each table
        ITableIterator iterator = dataSet.iterator();
        while (iterator.next()) {
            ITable table = iterator.getTable();

            // Do not process empty table
            if (isEmpty(table)) {
                continue;
            }

            List<String> uniqueKeys = getUniqueKeys(connection, table);

            if (uniqueKeys == null || uniqueKeys.size() == 0) {
                continue;
            }

            String sql = getSql(table, connection, uniqueKeys);

            executeSql(connection, factory, table, sql, uniqueKeys);
        }
    }

    private IStatementFactory getFactory(IDatabaseConnection connection) {
        DatabaseConfig databaseConfig = connection.getConfig();
        IStatementFactory factory =
                (IStatementFactory) databaseConfig.getProperty(DatabaseConfig.PROPERTY_STATEMENT_FACTORY);
        return factory;
    }

    private void executeSql(IDatabaseConnection connection, IStatementFactory factory, ITable table, String sql, List<String> uniqueKeys)
            throws DatabaseUnitException, SQLException {
        IPreparedBatchStatement statement = null;
        ITableMetaData metaData = getOperationMetaData(connection, table.getTableMetaData());
        Column[] columns = metaData.getColumns();
        int count = table.getRowCount();
        try {
            for (int i = 0; i < count; i++) {
                if (statement != null) {
                    statement.executeBatch();
                    statement.clearBatch();
                    statement.close();
                }
                statement = factory.createPreparedBatchStatement(
                        sql, connection);
                addBatchValue(table, statement, columns, uniqueKeys, i);
            }

            statement.executeBatch();
            statement.clearBatch();
        } finally {
            if (statement != null) {
                statement.close();
            }
        }
    }

    private void addBatchValue(ITable table, IPreparedBatchStatement statement, Column[] columns, List<String> uniqueKeys,
            int row)
            throws SQLException, DataSetException, TypeCastException {
        for (Column column : columns) {
            // 必须保证都大写
            String columnName = column.getColumnName();
            if (!uniqueKeys.contains(columnName)) {
                continue;
            }

            try {
                statement.addValue(table.getValue(row, columnName), column.getDataType());
            } catch (TypeCastException e) {
                throw new TypeCastException("Error casting value for table '"
                        + table.getTableMetaData().getTableName()
                        + "' and column '" + columnName + "'", e);
            }
        }
        statement.addBatch();
    }

    private String getSql(ITable table, IDatabaseConnection connection, List<String> uniqueKeys) throws SQLException {
        // 获取唯一键
        String tableName = table.getTableMetaData().getTableName();

        StringBuilder sql = new StringBuilder();
        sql.append("delete ").append(tableName).append(" where ");
        boolean first = true;
        for (String uniqueKey : uniqueKeys) {
            if (first) {
                first = false;
            } else {
                sql.append(" and ");
            }
            sql.append(uniqueKey).append("=").append("? ");
        }
        return sql.toString();
    }

    private List<String> getUniqueKeys(IDatabaseConnection connection, ITable table) throws SQLException,
            DataSetException {
        List<String> uniqueKeys = new ArrayList<String>();
        // 一种实现方式
        Column[] columns = table.getTableMetaData().getColumns();
        for (Column column : columns) {
            if ("unique".equals(column.getRemarks())) {
                uniqueKeys.add(column.getColumnName());
            }
        }
        return uniqueKeys;
    }

    static boolean isEmpty(ITable table) throws DataSetException {
        ...
    }

    static ITableMetaData getOperationMetaData(IDatabaseConnection connection,
            ITableMetaData metaData) throws DatabaseUnitException, SQLException {
        ...
    }
}


dbunit中很多代码没法重用, 再一次祭出copy大法.

通过上面的步骤基本可以让unitils+dbunit能够满足目前项目的测试需要了.
0
0
分享到:
评论

相关推荐

    dbunit使用必需Jar包

    在使用 DBUnit 时,有几个必需的 Jar 包是必不可少的,这些 Jar 包提供了 DBUnit 的核心功能以及与其交互所需的依赖。根据描述,这里有四个关键的 Jar 包: 1. **dbunit.jar**:这是 DBUnit 的主库,包含了所有的 ...

    DBUnit 进行单元测试

    这使得我们可以验证数据库操作是否按预期工作,确保代码对数据库的修改是正确的。 使用DBUnit,我们可以创建一个数据集(通常为XML或CSV格式),该数据集包含用于初始化数据库的记录。在测试开始前,这些数据会被...

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

    DBUnit 是一个开源的 Java 库,专门用于数据库测试,它是 xUnit 测试框架(如 JUnit)的一个扩展。在数据库驱动的项目中,DBUnit 可以帮助开发者确保数据库状态的一致性,使得测试更加可靠。DBUnit 2.4.7 版本包含了...

    dbunit-2.4.9 源码

    在版本 2.4.9 中,DBUnit 提供了一系列的功能,帮助开发者在进行单元测试时能够管理和操作数据库的数据状态。 DBUnit 的核心功能包括: 1. 数据初始化:在测试前,DBUnit 可以导入 CSV、XML 或 Excel 文件中的数据...

    DBUNIT使用

    在使用 DbUnit 进行数据库测试时,用户需要连接到真实数据库,并拥有对数据库或者至少是其中的模式(schema)的独占访问权限,以免不同的开发人员同时运行测试时相互冲突。 DbUnit 的使用场景非常广泛,例如可以...

    Dbunit数据库连接下载

    在描述中提到的"flash能过控制java来运用数据库",这可能是指通过Java的Flex或Adobe AIR接口,使Flash应用程序能够与Java后端进行交互,进而利用Dbunit对数据库进行操作。这样的组合可以让前端的富媒体应用具有强大...

    dbunit 多个版本.rar

    dbunit-2.2.3..jar dbunit-2.4.2.jar dbunit-2.5.3.jar dbunit-2.7.0.jar 发现每个版本对JDK是有要求的,比如2.7 只能用于JDK1.8版本,所以整理好几个jar包挑选适合自己的

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

    Dbunit是一款强大的Java库,它扩展了JUnit,为数据库提供了结构化数据的导入和导出,使得测试更加方便和可靠。下面将详细介绍如何在Spring中使用Dbunit进行Dao的集成单元测试。 首先,我们需要理解集成测试的概念。...

    dbunit2.2

    DBUnit 的主要功能在于帮助开发者在执行测试前后对数据库进行初始化和清理,确保每次测试都在一个已知的、干净的状态下开始。这样可以避免因为数据残留或依赖关系导致的测试不准确。以下是一些关于DBUnit 2.2的知识...

    dbunit帮助文档(HTML版)

    DBUnit是一个强大的工具,它允许开发者在测试用例之间对数据库进行"干净"的状态设置。这通常涉及到在每个测试之前清除数据,然后插入特定的测试数据。DBUnit通过使用XML或Flat CSV格式的数据集来管理这些操作,使得...

    DBUNIT 基类DBUNIT 基类DBUNIT 基类DBUNIT 基类DBUNIT 基类

    DBUNIT 基类DBUNIT 基类DBUNIT 基类DBUNIT 基类DBUNIT 基类DBUNIT 基类DBUNIT 基类DBUNIT 基类DBUNIT 基类DBUNIT 基类DBUNIT 基类DBUNIT 基类DBUNIT 基类DBUNIT 基类DBUNIT 基类DBUNIT 基类DBUNIT 基类DBUNIT 基类...

    dbunit开发文档

    DBUnit 是一个开源的 Java 库,专门用于数据库测试,它是 xUnit 测试框架(如 JUnit)的一个扩展。在数据库驱动的项目中,DBUnit 提供了一种结构化的方法来设置和验证数据库状态,从而确保测试的一致性和可靠性。这...

    dbunit测试demo

    标题“dbunit测试demo”表明这是一个关于如何使用 DBUnit 进行测试的实例。描述中提到的链接指向了一篇博客文章,这可能包含了一个具体的 DBUnit 测试用例的详细步骤和解释。尽管我们无法直接访问该链接,但我们可以...

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

    DbUnit 是一个针对数据库驱动项目的JUnit扩展,同时也可用于Ant构建工具。它的主要功能是能够将数据库数据导出到XML数据集,并从XML数据集中导入,从而支持数据库的测试和数据管理。DbUnit还允许你验证数据库中的...

    junit4.10+dbunit2.4.7+httpunit+junitperf的jar包

    这样,即使测试对数据库进行了修改,也可以在测试结束后恢复原始状态。此外,DbUnit还支持各种操作,如比较数据库的实际状态与期望状态,以验证数据的正确性。 3. HttpUnit: HttpUnit是一个用于Web应用模拟客户端...

    dbunit-2.0-src.zip_dbunit src_dbunit-2.1-src

    DBUnit 是一个强大的Java库,专门用于数据库的测试。它扩展了JUnit,使得数据库相关的单元测试变得更加简单和规范。在给定的压缩包文件中,我们有两个版本的DBUnit源码:`dbunit-2.0-src` 和 `dbunit-2.1-src`。这些...

    通过DBUNIT做批量对比测试

    在软件开发中,测试是保证代码质量和功能正确性的重要环节,而 DBUNIT 尤其适用于对数据库操作进行测试,特别是在进行批量数据处理时。 批量对比测试在数据库测试中扮演着关键角色,因为往往需要确保大量的数据导入...

Global site tag (gtag.js) - Google Analytics