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.
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 Zeilensql_col_<N>— Spaltennamensql_<row>_<col>— Wertesql_result— erster Wertsql_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.
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:
- In CLI werden Pfade vom Arbeitsverzeichnis aufgelöst, nicht von der JMX
- In CI/CD
cdins 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
Noch keine Kommentare.