返回首页

JMX JMeter 分解:从 50k 到 5k 行

本文描述了 JMeter 中 50 000 行的单体 JMX 文件的重构。将 SQL 提取到 .sql 并进行变量替换,Groovy 到文件,通过 Test Fragment 实现模块化。结果:大小缩小 10 倍,便于代码审查。

JMeter JMX 单体到模块:重构 10 倍
Advertisement 728x90

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}

Google AdInline article slot

解决方案是使用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文件。在设置中,启用缓存编译脚本(如果可用) — 不启用此选项,每次调用都编译会降低性能。

Google AdInline article slot

通用工具(sql_executor.groovy、json_utils.groovy)放在common文件夹中,所有测试均可访问。

通过测试片段和包含控制器实现模块化

按领域划分的线程组保存为测试片段(右键单击 → 另存为测试片段)。通过包含控制器将它们添加到主JMX中。

注意事项:

Google AdInline article slot
  • 在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

Advertisement 728x90

继续阅读