Zpět na domů

Dekompozice JMX JMeter: z 50k do 5k řádků

Článek popisuje refaktoring monolitního JMX souboru JMeter na 50 000 řádků. Vyjmutí SQL do .sql s substitucí proměnných, Groovy do souborů, modularita přes Test Fragment. Výsledek: velikost 10x menší, pohodlný code review.

JMeter JMX monolit do modulů: refaktoring na 10x
Advertisement 728x90

Dekompozice JMeter testů: JMX soubor o 50 000 řádcích zredukován na 5 000

Monolitické JMX soubory JMeter s více než 50 000 řádky komplikují code review, úpravy a údržbu. Soubor se nevejde do paměti, merge konflikty při paralelní práci jsou nevyhnutelné a diff v PR o 3000 řádcích XML je nečitelný. JSR223 Groovy a SQL skripty uvnitř JMX postrádají zvýraznění syntaxe v IDE.

Dekompozice zredukovala JMX na 5000 řádků, logika testů byla přesunuta do samostatných souborů. Testování endpointu se zkrátilo z 900 na 300 řádků. Struktura projektu se stala modulární, jako v běžném kódu.

Přesun SQL skriptů do samostatných souborů

JDBC Request ukládá SQL uvnitř JMX bez zvýraznění syntaxe. Funkce __FileToString čte .sql soubory, ale nerozvíjí JMeter proměnné ${varName} z vars.

Google AdInline article slot

Řešením je univerzální JSR223 Sampler v Groovy. Skript dosadí proměnné, provede SELECT/DML a uloží výsledky do vars:

  • sql_result_count — počet řádků
  • sql_col_<N> — názvy sloupců
  • sql_<řádek>_<sloupec> — hodnoty
  • sql_result — první hodnota
  • sql_affected_rows — změněné řádky pro DML
import groovy.sql.Sql
import java.util.regex.Pattern

// ============================================================
// JMeter JSR223 Sampler - Univerzální SQL executor
// ============================================================
// Parametry (oddělené mezerou):
//   args[0] = JDBC URL      (jdbc:postgresql://host:5432/db)
//   args[1] = uživatelské jméno DB
//   args[2] = heslo DB
//   args[3] = cesta k .sql souboru (lze i s mezerami)
//
// Výsledek se zapíše do JMeter vars:
//   sql_result_count    - počet řádků
//   sql_col_count       - počet sloupců
//   sql_col_<N>         - název sloupce N (index od 1)
//   sql_<řádek>_<sloupec>     - hodnota (index od 1, např. sql_1_1)
//   sql_result          - první hodnota (zkratka pro skalární hodnotu)
// ============================================================

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

// --- Čtení SQL ze souboru ---
def rawSql = new File(sqlFile).getText("UTF-8")

// --- Dosazení ${varName} -> vars.get("varName") ---
def resolved = rawSql.replaceAll(/\$\{(\w+)\}/) { fullMatch, varName ->
    def value = vars.get(varName)
    if (value == null) {
        log.warn("Proměnná '${varName}' nebyla nalezena v JMeter vars, ponechává se původní")
        return fullMatch
    }
    return value
}

log.info("Provádím SQL:\n${resolved}")

// --- Připojení a provedení ---
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 - očekává se 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 vrátilo 0 řádků")
            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 vrátilo ${rows.size()} řádek(ů), ${colNames.size()} sloupc(ů)")

    } else {
        // --- DML (INSERT/UPDATE/DELETE/MERGE atd.) ---
        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 provedeno, ovlivněné řádky: ${affected}")
    }

} catch (Exception e) {
    log.error("Provedení SQL selhalo: ${e.message}", e)
    throw e
} finally {
    sql.close()
}

Parametry v JSR223 Sampler: JDBC URL, uživatelské jméno, heslo, cesta k .sql. Relativní cesty fungují v GUI i CLI při shodě pracovních adresářů.

Groovy skripty mimo JMX

JSR223 prvky se přesouvají do .groovy souborů. V nastavení se zapne Cache compiled script if available — bez toho kompilace při každém volání snižuje výkon.

Google AdInline article slot

Společné utility (sql_executor.groovy, json_utils.groovy) jsou ve složce common a jsou dostupné všem testům.

Modularita pomocí Test Fragment a Include Controller

Thread Group podle domén se uloží jako Test Fragment (PCM → Save as Test Fragment). Do hlavního JMX se přidají pomocí Include Controller.

Úskalí:

Google AdInline article slot
  • V CLI se cesty řeší od pracovního adresáře, ne od JMX
  • V CI/CD je třeba nastavit cd do adresáře projektu

Struktura projektu po refaktoringu

Předtím:

project/
└── src/test/jmeter/
    └── test_plan.jmx            # 50 000+ řádků

Nyní:

project/
└── src/test/jmeter/
    ├── test_plan.jmx                        # ~5 000 řádků
    ├── 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 obsahuje pouze sekvenci provádění. Logika je ve specializovaných souborech s podporou IDE.

Co je důležité

  • Velikost JMX se zmenšila 10×: 50 000 → 5000 řádků
  • SQL a Groovy mimo JMX s zvýrazněním syntaxe a automatickým doplňováním
  • Test Fragment + Include Controller pro modularitu
  • Univerzální SQL skript s dosazením ${varName}
  • Cache compiled script je povinný pro Groovy

— Editorial Team

Advertisement 728x90

Číst dál