Jmeter 二次开发解决 JDBC 返回结果可读性问题

Posted 酔清风

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jmeter 二次开发解决 JDBC 返回结果可读性问题相关的知识,希望对你有一定的参考价值。

篇幅较长,请大家耐心看完,建议收藏慢慢看,另外文末给大家准备了面试资料(面试题加学习资料等),需要自取!

一、背景

在使用 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 格式与接口的返回结果是相似的,这样不管是功能测试手动执行后查看结果还是自动化测试流程验证都很方便。

福利

以上是关于Jmeter 二次开发解决 JDBC 返回结果可读性问题的主要内容,如果未能解决你的问题,请参考以下文章

Jmeter常用脚本开发之JDBC请求

Jmeter------将JDBC Request的查询结果作为另一个接口的请求参数

Jmeter的JDBC Request,sql参数化及返回值取值

性能工具之Jmeter JDBC Request学习

Jmeter 接口测试对上接口结果二次处理作为入参 Bean Shell Processor

jmeter—JDBC request动态参数设置