一、背景
在使用jemter进行测试的过程中,会经常使用到JDBC Request组件执行sql语句,获取数据库表中的数据来验证业务功能逻辑。一般常用业务表的字段都至少在几十个以上,但是JDBC Request的返回结果是那种字符无分割的表格形式,JDBC显示的结果就是非常杂乱的,根本就很难找到字段对应的值,使用起来非常不方便。(如下图)
基于上面的痛点,对jmeter源码进行二次开发,修改JDBC Request的返回结果为数据格式比较简单、易于读写、易于解析的json格式。
二、源码改造过程
一)、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 主页面了
二)、改造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包。
三)、将改造完成的jar包集成到本地jemter
将改造完的的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格式与接口的返回结果是相似的,这样不管是功能测试手动执行后查看结果还是自动化测试流程验证都很方便。