一、背景

在使用 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 验证

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,效果如下

四、总结

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

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

更多详情可加微信 :


↙↙↙阅读原文可查看相关链接,并与作者交流