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