Zurück zur Startseite

JMX JMeter Zerlegung: von 50k auf 5k Zeilen

Der Artikel beschreibt das Refactoring einer monolithischen JMX-Datei in JMeter mit 50 000 Zeilen. SQL zu .sql mit Variablensubstitution extrahieren, Groovy zu Dateien, Modularität via Test Fragment. Ergebnis: Größe 10-mal kleiner, bequeme Code-Review.

JMeter JMX-Monolith zu Modulen: Refactoring 10x
Advertisement 728x90

JMeter-Tests zerlegen: Von 50.000 auf 5.000 Zeilen in JMX

Monolithische JMX-Dateien in JMeter mit über 50.000 Zeilen machen Code-Reviews, Bearbeitung und Wartung zum Albtraum. Die Datei passt nicht in den Speicher, Merge-Konflikte bei paralleler Arbeit sind unvermeidlich, und ein 3.000-Zeilen-XML-Diff in einem PR ist unlesbar. In JMX eingebettete JSR223-Groovy- und SQL-Skripte haben keine IDE-Syntaxhervorhebung.

Durch Zerlegung wurde die JMX auf 5.000 Zeilen reduziert, indem Testlogik in separate Dateien ausgelagert wurde. Das Testen eines Endpunkts schrumpfte von 900 auf 300 Zeilen. Die Projektstruktur wurde modular, genau wie regulärer Code.

SQL-Skripte in separate Dateien auslagern

JDBC Request speichert SQL innerhalb von JMX ohne Hervorhebung. Die Funktion __FileToString liest .sql-Dateien, expandiert aber keine JMeter-Variablen ${varName} aus vars.

Google AdInline article slot

Die Lösung ist ein universeller JSR223 Sampler in Groovy. Das Skript ersetzt Variablen, führt SELECT/DML aus und speichert Ergebnisse in vars:

  • sql_result_count — Anzahl der Zeilen
  • sql_col_<N> — Spaltennamen
  • sql_<row>_<col> — Werte
  • sql_result — erster Wert
  • sql_affected_rows — betroffene Zeilen für DML
import groovy.sql.Sql
import java.util.regex.Pattern

// ============================================================
// JMeter JSR223 Sampler - Universeller SQL-Executor
// ============================================================
// Parameter (leerzeichengetrennt):
//   args[0] = JDBC-URL      (jdbc:postgresql://host:5432/db)
//   args[1] = DB-Benutzername
//   args[2] = DB-Passwort
//   args[3] = Pfad zur .sql-Datei (kann Leerzeichen enthalten)
//
// Ergebnisse werden in JMeter vars geschrieben:
//   sql_result_count    - Zeilenanzahl
//   sql_col_count       - Spaltenanzahl
//   sql_col_<N>         - Spaltenname N (1-basiert)
//   sql_<row>_<col>     - Wert (1-basiert, z.B. sql_1_1)
//   sql_result          - erster Wert (Skalar-Kurzform)
// ============================================================

def jdbcUrl  = args[0]
def dbUser   = args[1]
def dbPass   = args[2]
def sqlFile  = args[3..args.length - 1].join(" ")

// --- SQL aus Datei lesen ---
def rawSql = new File(sqlFile).getText("UTF-8")

// --- ${varName} -> vars.get("varName") ersetzen ---
def resolved = rawSql.replaceAll(/\$\{(\w+)\}/) { fullMatch, varName ->
    def value = vars.get(varName)
    if (value == null) {
        log.warn("Variable '${varName}' nicht in JMeter vars gefunden, bleibt unverändert")
        return fullMatch
    }
    return value
}

log.info("Führe SQL aus:\n${resolved}")

// --- Verbinden und ausführen ---
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 - erwarte ResultSet ---
        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 lieferte 0 Zeilen")
            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 lieferte ${rows.size()} Zeile(n), ${colNames.size()} Spalte(n)")

    } else {
        // --- DML (INSERT/UPDATE/DELETE/MERGE, etc.) ---
        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 ausgeführt, betroffene Zeilen: ${affected}")
    }

} catch (Exception e) {
    log.error("SQL-Ausführung fehlgeschlagen: ${e.message}", e)
    throw e
} finally {
    sql.close()
}

Parameter in JSR223 Sampler: JDBC-URL, Benutzername, Passwort, Pfad zu .sql. Relative Pfade funktionieren in GUI und CLI, wenn Arbeitsverzeichnisse übereinstimmen.

Groovy-Skripte außerhalb von JMX

JSR223-Elemente werden in .groovy-Dateien ausgelagert. In den Einstellungen Cache compiled script if available aktivieren – ohne dies reduziert Kompilierung bei jedem Aufruf die Leistung.

Google AdInline article slot

Gemeinsame Utilities (sql_executor.groovy, json_utils.groovy) kommen in einen common-Ordner, der für alle Tests zugänglich ist.

Modularität über Test Fragment und Include Controller

Thread Groups nach Domäne werden als Test Fragments gespeichert (Rechtsklick → Save as Test Fragment). Sie werden über Include Controller zur Haupt-JMX hinzugefügt.

Fallstricke:

Google AdInline article slot
  • In CLI werden Pfade vom Arbeitsverzeichnis aufgelöst, nicht von der JMX
  • In CI/CD cd ins Projektverzeichnis setzen

Projektstruktur nach Refactoring

Vorher:

project/
└── src/test/jmeter/
    └── test_plan.jmx            # 50.000+ Zeilen

Nachher:

project/
└── src/test/jmeter/
    ├── test_plan.jmx                        # ~5.000 Zeilen
    ├── 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 enthält nur die Ausführungssequenz. Logik ist in spezialisierten Dateien mit IDE-Unterstützung.

Wichtigste Erkenntnisse

  • JMX-Größe um das 10-fache reduziert: 50.000 → 5.000 Zeilen
  • SQL und Groovy außerhalb von JMX mit Hervorhebung und Autovervollständigung
  • Test Fragment + Include Controller für Modularität
  • Universelles SQL-Skript mit ${varName}-Ersetzung
  • Cache compiled script ist für Groovy essenziell

— Editorial Team

Advertisement 728x90

Weiterlesen