Dekompozycja testów JMeter: JMX z 50 000 linii do 5 000
Monolityczne pliki JMX JMeter z ponad 50 000 linii utrudniają code review, edycję i utrzymanie. Plik nie mieści się w pamięci, konflikty merge przy pracy równoległej są nieuniknione, diff w PR na 3000 linii XML jest nieczytelny. Skrypty JSR223 Groovy i SQL wewnątrz JMX pozbawione są podświetlania w IDE.
Dekompozycja zmniejszyła JMX do 5000 linii, logikę testów przeniesiono do oddzielnych plików. Testowanie endpointu skróciło się z 900 do 300 linii. Struktura projektu stała się modułowa, jak w zwykłym kodzie.
Przeniesienie skryptów SQL do oddzielnych plików
JDBC Request przechowuje SQL wewnątrz JMX bez podświetlania. Funkcja __FileToString odczytuje pliki .sql, ale nie rozpoznaje zmiennych JMeter ${varName} z vars.
Rozwiązanie — uniwersalny JSR223 Sampler w Groovy. Skrypt podstawia zmienne, wykonuje SELECT/DML, zapisuje wyniki w vars:
sql_result_count— liczba wierszysql_col_<N>— nazwy kolumnsql_<wiersz>_<kolumna>— wartościsql_result— pierwsza wartośćsql_affected_rows— zmienione wiersze dla DML
import groovy.sql.Sql
import java.util.regex.Pattern
// ============================================================
// JMeter JSR223 Sampler - Uniwersalny wykonawca SQL
// ============================================================
// Parametry (oddzielone spacją):
// args[0] = JDBC URL (jdbc:postgresql://host:5432/db)
// args[1] = nazwa użytkownika bazy danych
// args[2] = hasło bazy danych
// args[3] = ścieżka do pliku .sql (może zawierać spacje)
//
// Wynik zapisywany w JMeter vars:
// sql_result_count - liczba wierszy
// sql_col_count - liczba kolumn
// sql_col_<N> - nazwa kolumny N (1-based)
// sql_<wiersz>_<kolumna> - wartość (1-based, np. sql_1_1)
// sql_result - pierwsza wartość (skrót skalarny)
// ============================================================
def jdbcUrl = args[0]
def dbUser = args[1]
def dbPass = args[2]
def sqlFile = args[3..args.length - 1].join(" ")
// --- Odczytujemy SQL z pliku ---
def rawSql = new File(sqlFile).getText("UTF-8")
// --- Podstawiamy ${varName} -> vars.get("varName") ---
def resolved = rawSql.replaceAll(/\$\{(\w+)\}/) { fullMatch, varName ->
def value = vars.get(varName)
if (value == null) {
log.warn("Zmienna '${varName}' nie znaleziona w JMeter vars, pozostawiamy bez zmian")
return fullMatch
}
return value
}
log.info("Wykonywanie SQL:\n${resolved}")
// --- Łączymy się i wykonujemy ---
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 - oczekujemy 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 zwrócił 0 wierszy")
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 zwrócił ${rows.size()} wiersz(e), ${colNames.size()} kolumn(y)")
} else {
// --- DML (INSERT/UPDATE/DELETE/MERGE itp.) ---
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 wykonane, zmienione wiersze: ${affected}")
}
} catch (Exception e) {
log.error("Wykonanie SQL nie powiodło się: ${e.message}", e)
throw e
} finally {
sql.close()
}
Parametry w JSR223 Sampler: JDBC URL, nazwa użytkownika, hasło, ścieżka do .sql. Ścieżki względne działają w GUI i CLI przy zgodności katalogów roboczych.
Skrypty Groovy poza JMX
Elementy JSR223 przenosi się do plików .groovy. W ustawieniach włącza się Cache compiled script if available — bez tego kompilacja przy każdym wywołaniu obniża wydajność.
Wspólne narzędzia (sql_executor.groovy, json_utils.groovy) w folderze common, dostępne dla wszystkich testów.
Modularność przez Test Fragment i Include Controller
Thread Group według domen zapisuje się jako Test Fragment (PPM → Save as Test Fragment). Do głównego JMX dodaje się przez Include Controller.
Pułapki:
- W CLI ścieżki rozwiązywane są od katalogu roboczego, nie od JMX
- W CI/CD ustawiać
cddo katalogu projektu
Struktura projektu po refaktoryzacji
Było:
project/
└── src/test/jmeter/
└── test_plan.jmx # 50 000+ linii
Stało się:
project/
└── src/test/jmeter/
├── test_plan.jmx # ~5 000 linii
├── 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 zawiera tylko sekwencję wykonania. Logika w wyspecjalizowanych plikach z obsługą IDE.
Co jest ważne
- Rozmiar JMX zmniejszył się 10-krotnie: 50 000 → 5000 linii
- SQL i Groovy poza JMX z podświetlaniem i autouzupełnianiem
- Test Fragment + Include Controller dla modularności
- Uniwersalny skrypt SQL z podstawianiem
${varName} - Cache compiled script obowiązkowy dla Groovy
— Editorial Team
Brak komentarzy.