性能测试工具 Jmeter 二次开发解决 JDBC 返回结果可读性问题

中通科技测试 for 上海中通吉网络技术有限公司 · 2021年09月15日 · 最后由 在路上 回复于 2021年09月23日 · 3856 次阅读

一、背景

在使用 jmeter 进行测试的过程中,会经常使用到 JDBC Request 组件执行 sql 语句,获取数据库表中的数据来验证业务功能逻辑。一般常用业务表的字段都至少在几十个以上,但是 JDBC Request 的返回结果是那种字符无分割的表格形式,JDBC 显示的结果非常杂乱,很难找到字段对应的值,使用起来非常不方便。(如下图)

基于上面的痛点,对 jmeter 源码进行二次开发,修改 JDBC Request 的返回结果为数据格式比较简单、易于读写、易于解析的 json 格式。

二、源码改造过程

1.jmeter 源码导入 idea

(1) 官网上下载 apache-jmeter-5.4.1_src.zip,

源码下载完成后,解压到指定目录,注意,解压后的文件没有网上很多教程中说的两个 eclipse 文件,也没有 ant 的 build.xml,5.4 是基于 Gradle 的。

(2) 导入 idea,gradle

解压完成后,打开 IDEA,然后 File--》Open 打开解压的源码,选择 bin 目录的上级目录打开,打开完成后,idea 会在右下角弹出找到 Gradle build script,此时,点击 Import Gradle Project,IDEA 会自动根据配置文件去下载所需要的 jar 以及 Gradle 等支持软件。

(3)运行 jmeter 验证

  • 从 Jmeter 的启动类 NewDriver 类中启动 Jmeter。

  • 也可以执行 developement 下的 runGui,执行完成后就看到 Jmeter 主页面了。

2.改造 JDBC 请求的返回结果

JDBC 请求的返回结果处理逻辑在 package org.apache.jmeter.protocol.jdbc 下的 AbstractJDBCTestElement 类,所以在此类中注释掉原有的处理过程再替换上新的处理逻辑,具体修改 getStringFromResultSet 方法和 processRow 方法,如下:

 /**
     * Gets a Data object from a ResultSet.
     *
     * @param rs ResultSet passed in from a database query
     * @return a Data object
     * @throws java.sql.SQLException
     * @throws UnsupportedEncodingException
     */
    private String getStringFromResultSet(ResultSet rs) throws SQLException, UnsupportedEncodingException {
        ResultSetMetaData meta = rs.getMetaData();
        StringBuilder sb = new StringBuilder();
        int numColumns = meta.getColumnCount();//ResultSet 对象中的列数
    //        for (int i = 1; i <= numColumns; i++) {
    //            sb.append(meta.getColumnLabel(i));
    //            if (i == numColumns) {
    //                sb.append('\n');
    //            } else {
    //                sb.append('\t');
    //            }
    //        }
        sb.append("[\n");
        JMeterVariables jmvars = getThreadContext().getVariables();
        String[] varNames = getVariableNames().split(COMMA);
        String currentResultVariable = getResultVariable().trim();
        List<Map<String, Object>> results = null;
        if (!currentResultVariable.isEmpty()) {
            results = new ArrayList<>();
            jmvars.putObject(currentResultVariable, results);
        }
        int currentIterationIndex = 0;
        int resultSetMaxRows = getIntegerResultSetMaxRows();
        if (resultSetMaxRows < 0) {
            while (rs.next()) {
                currentIterationIndex = processRow(rs, meta, sb, numColumns, jmvars, varNames, results, currentIterationIndex);
            }
        } else {
            while (currentIterationIndex < resultSetMaxRows && rs.next()) {
                currentIterationIndex = processRow(rs, meta, sb, numColumns, jmvars, varNames, results, currentIterationIndex);
            }
        }
        // Remove any additional values from previous sample
        for (String varName : varNames) {
            String name = varName.trim();
            if (name.length() > 0 && jmvars != null) {
                final String varCount = name + "_#"; // $NON-NLS-1$
                // Get the previous count
                String prevCount = jmvars.get(varCount);
                if (prevCount != null) {
                    int prev = Integer.parseInt(prevCount);
                    for (int n = currentIterationIndex + 1; n <= prev; n++) {
                        jmvars.remove(name + UNDERSCORE + n);
                    }
                }
                jmvars.put(varCount, Integer.toString(currentIterationIndex)); // save the current count
            }
        }
        if (sb.length() > 3) {
            sb.delete(sb.length() - 2, sb.length());
        }
        sb.append("\n]");
        return sb.toString();
    }

private int processRow(ResultSet rs, ResultSetMetaData meta, StringBuilder sb, int numColumns,
                           JMeterVariables jmvars, String[] varNames, List<Map<String, Object>> results, int currentIterationIndex)
            throws SQLException, UnsupportedEncodingException {
        Map<String, Object> row = null;
        currentIterationIndex++;
        sb.append("  {\n");
        for (int i = 1; i <= numColumns; i++) {
            Object o = rs.getObject(i);
    //         if (results != null) {
    //             if (row == null) {
    //                 row = new HashMap<>(numColumns);
    //                 results.add(row);
    //             }
    //             row.put(meta.getColumnLabel(i), o);
    //         }
    //         if (o instanceof byte[]) {
    //             o = new String((byte[]) o, ENCODING);
    //         }
    //         sb.append(o);
    //         if (i == numColumns) {
    //             sb.append('\n');
    //         } else {
    //             sb.append('\t');
    //         }

            String columnName = meta.getColumnLabel(i);
            sb.append("    \"");
            sb.append(columnName);
            sb.append("\": ");
    //        sb.append(o);
            if (o == null) {
                sb.append("null");
            } else if (meta.getColumnType(i) == java.sql.Types.VARCHAR) {
                sb.append("\"").append(rs.getNString(columnName)).append("\"");
            } else if (meta.getColumnType(i) == Types.NULL) {
                sb.append(rs.getObject(columnName));
            } else if (meta.getColumnType(i) == Types.BIT) {
                sb.append(rs.getInt(columnName));
            } else if (meta.getColumnType(i) == Types.INTEGER) {
                sb.append(rs.getInt(columnName));
            } else if (meta.getColumnType(i) == java.sql.Types.BIGINT) {
                sb.append(rs.getLong(columnName));
            } else if (meta.getColumnType(i) == java.sql.Types.BOOLEAN) {
                sb.append(rs.getBoolean(columnName));
            } else if (meta.getColumnType(i) == java.sql.Types.BLOB) {
                sb.append(rs.getBlob(columnName));
            } else if (meta.getColumnType(i) == java.sql.Types.DOUBLE) {
                sb.append(rs.getDouble(columnName));
            } else if (meta.getColumnType(i) == java.sql.Types.FLOAT) {
                sb.append(rs.getFloat(columnName));
            } else if (meta.getColumnType(i) == java.sql.Types.TINYINT) {
                sb.append(rs.getInt(columnName));
            } else if (meta.getColumnType(i) == java.sql.Types.SMALLINT) {
                sb.append(rs.getInt(columnName));
            } else if (meta.getColumnType(i) == Types.TIMESTAMP) {
                sb.append(rs.getTimestamp(columnName));
            } else {
                sb.append("\"").append(rs.getObject(columnName)).append("\"");
            }
            sb.append(",\n");
            if (i <= varNames.length) { // i starts at 1
                String name = varNames[i - 1].trim();
                if (name.length() > 0) { // Save the value in the variable if present
                    jmvars.put(name + UNDERSCORE + currentIterationIndex, o == null ? null : o.toString());
                }
            }
        }
        sb.delete(sb.length() - 2, sb.length());
        sb.append("\n  },\n");

        return currentIterationIndex;
    }

代码修改完成后,启动运行 idea 中的 jmeter 验证。

验证通过后,在 jdbc 下的 build-libs 目录生成 jmeter_jdbc 的 jar 包。

3.将改造完成的 jar 包集成到本地 jmeter

将改造完的的 jar 复制到 jmeter 的\lib\ext 目录下,覆盖原有 jar 包(删除原 jar 包前还请先备份,源码版本是 5.4.1),见下图:

三、源码改造完成后的使用效果

启动本地集成了二次开发的 jdbc 的 jmeter,效果如下

  • JDBC Request,查询表中数据信息:

  • JSON 提取器,获取 JDBC 返回结果 json 中的字段值:

  • 后置处理器:利用 fastjson 处理 JDBC 返回的 json,获取数据中指定的字段值:

  • 查看结果树,JDBC Request 返回结果为 json 格式的展示效果:

四、总结

至此关于 Jmeter 的 JDBC Request 源码改造完成,解决了 JDBC Sampler 返回结果显示杂乱的问题,极大的提升了使用体验。

JDBC Request 整体返回格式是一个 JSONArray,每一条数据为一个 JSONObject,这种返回的 json 格式与接口的返回结果是相似的,这样不管是功能测试手动执行后查看结果还是自动化测试流程验证都很方便。

更多详情可加微信 :

共收到 1 条回复 时间 点赞

很好的案例哈,赞赞赞,进步就是站在巨人肩膀上,一点点的不断改进。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册