JMeter测试解构:从5万行JMX缩减至5千行
JMeter中超过5万行的单体JMX文件让代码审查、编辑和维护变得噩梦般困难。文件无法装入内存,并行工作时的合并冲突不可避免,PR中3000行的XML差异难以阅读。嵌入JMX的JSR223 Groovy和SQL脚本缺乏IDE语法高亮。
通过解构,JMX缩减至5千行,测试逻辑移至独立文件。测试一个端点从900行减少到300行。项目结构变得模块化,就像常规代码一样。
将SQL脚本提取到独立文件
JDBC请求将SQL存储在JMX中,没有高亮显示。__FileToString函数读取.sql文件,但不扩展来自vars的JMeter变量${varName}。
解决方案是使用Groovy编写的通用JSR223采样器。脚本替换变量,执行SELECT/DML,并将结果保存到vars:
sql_result_count— 行数sql_col_<N>— 列名sql_<row>_<col>— 值sql_result— 第一个值sql_affected_rows— DML影响的行数
import groovy.sql.Sql
import java.util.regex.Pattern
// ============================================================
// JMeter JSR223采样器 - 通用SQL执行器
// ============================================================
// 参数(空格分隔):
// args[0] = JDBC URL (jdbc:postgresql://host:5432/db)
// args[1] = 数据库用户名
// args[2] = 数据库密码
// args[3] = .sql文件路径(可包含空格)
//
// 结果写入JMeter变量:
// sql_result_count - 行数
// sql_col_count - 列数
// sql_col_<N> - 列名N(基于1)
// sql_<row>_<col> - 值(基于1,例如sql_1_1)
// sql_result - 第一个值(标量快捷方式)
// ============================================================
def jdbcUrl = args[0]
def dbUser = args[1]
def dbPass = args[2]
def sqlFile = args[3..args.length - 1].join(" ")
// --- 从文件读取SQL ---
def rawSql = new File(sqlFile).getText("UTF-8")
// --- 替换${varName} -> vars.get("varName") ---
def resolved = rawSql.replaceAll(/\$\{(\w+)\}/) { fullMatch, varName ->
def value = vars.get(varName)
if (value == null) {
log.warn("变量'${varName}'在JMeter变量中未找到,保留原样")
return fullMatch
}
return value
}
log.info("执行SQL:\n${resolved}")
// --- 连接并执行 ---
def sql = Sql.newInstance(jdbcUrl, dbUser, dbPass, "org.postgresql.Driver")
try {
def trimmed = resolved.stripIndent().trim()
def isSelect = trimmed.toUpperCase() =~ /^\s*(SELECT|WITH|VALUES)\b/
if (isSelect) {
// --- SELECT / WITH / VALUES - 预期结果集 ---
def rows = sql.rows(resolved)
if (rows == null || rows.isEmpty()) {
vars.put("sql_result_count", "0")
vars.put("sql_col_count", "0")
vars.put("sql_result", "")
log.info("SQL返回0行")
return
}
def colNames = rows[0].keySet().toList()
vars.put("sql_result_count", String.valueOf(rows.size()))
vars.put("sql_col_count", String.valueOf(colNames.size()))
colNames.eachWithIndex { name, idx ->
vars.put("sql_col_${idx + 1}", name)
}
rows.eachWithIndex { row, rowIdx ->
colNames.eachWithIndex { col, colIdx ->
def val = row[col]
vars.put("sql_${rowIdx + 1}_${colIdx + 1}", val != null ? val.toString() : "")
}
}
def firstVal = rows[0][colNames[0]]
vars.put("sql_result", firstVal != null ? firstVal.toString() : "")
log.info("SQL返回${rows.size()}行,${colNames.size()}列")
} else {
// --- DML(INSERT/UPDATE/DELETE/MERGE等) ---
def affected = sql.executeUpdate(resolved)
vars.put("sql_result_count", "0")
vars.put("sql_col_count", "0")
vars.put("sql_result", "")
vars.put("sql_affected_rows", String.valueOf(affected))
log.info("DML执行完成,影响行数:${affected}")
}
} catch (Exception e) {
log.error("SQL执行失败:${e.message}", e)
throw e
} finally {
sql.close()
}
JSR223采样器中的参数:JDBC URL、用户名、密码、.sql路径。如果工作目录匹配,相对路径在GUI和CLI中有效。
将Groovy脚本移出JMX
JSR223元素移至.groovy文件。在设置中,启用缓存编译脚本(如果可用) — 不启用此选项,每次调用都编译会降低性能。
通用工具(sql_executor.groovy、json_utils.groovy)放在common文件夹中,所有测试均可访问。
通过测试片段和包含控制器实现模块化
按领域划分的线程组保存为测试片段(右键单击 → 另存为测试片段)。通过包含控制器将它们添加到主JMX中。
注意事项:
- 在CLI中,路径从工作目录解析,而非JMX文件位置
- 在CI/CD中,设置
cd到项目目录
重构后的项目结构
重构前:
project/
└── src/test/jmeter/
└── test_plan.jmx # 5万+行
重构后:
project/
└── src/test/jmeter/
├── test_plan.jmx # 约5千行
├── common/
│ ├── sql_executor.groovy
│ └── json_utils.groovy
├── domain_a/
│ ├── domain_a_fragment.jmx
│ ├── create_entity.sql
│ ├── cleanup.sql
│ ├── pre_processing.groovy
│ └── response_validation.groovy
├── domain_b/
│ ├── domain_b_fragment.jmx
│ ├── prepare_data.sql
│ └── check_result.groovy
└── domain_c/
├── domain_c_fragment.jmx
├── init.sql
└── assertions.groovy
JMX仅包含执行顺序。逻辑位于具有IDE支持的专业文件中。
关键要点
- JMX大小减少10倍:从5万行到5千行
- SQL和Groovy移出JMX,支持高亮和自动补全
- 测试片段 + 包含控制器实现模块化
- 通用SQL脚本支持
${varName}替换 - 缓存编译脚本对Groovy至关重要
— Editorial Team
暂无评论。