Retour à l'accueil

Décomposition JMX JMeter : de 50k à 5k lignes

L'article décrit le refactoring d'un fichier JMX monolithique dans JMeter avec 50 000 lignes. Extraire SQL vers .sql avec substitution de variables, Groovy vers fichiers, modularité via Test Fragment. Résultat : taille 10 fois plus petite, code review pratique.

JMeter JMX monolithe vers modules : refactoring 10x
Advertisement 728x90

Décomposition des tests JMeter : De 50 000 à 5 000 lignes dans les fichiers JMX

Les fichiers JMX monolithiques dans JMeter avec plus de 50 000 lignes rendent les revues de code, les modifications et la maintenance cauchemardesques. Le fichier ne tient pas en mémoire, les conflits de fusion lors de travaux parallèles sont inévitables, et un diff XML de 3 000 lignes dans une PR est illisible. Les scripts JSR223 Groovy et SQL intégrés dans le JMX manquent de coloration syntaxique dans l'IDE.

La décomposition a réduit le JMX à 5 000 lignes, en déplaçant la logique de test dans des fichiers séparés. Tester un endpoint est passé de 900 à 300 lignes. La structure du projet est devenue modulaire, comme du code classique.

Extraction des scripts SQL dans des fichiers séparés

La requête JDBC stocke le SQL dans le JMX sans coloration. La fonction __FileToString lit les fichiers .sql mais ne développe pas les variables JMeter ${varName} de vars.

Google AdInline article slot

La solution est un échantillonneur JSR223 universel en Groovy. Le script substitue les variables, exécute SELECT/DML, et sauvegarde les résultats dans vars :

  • sql_result_count — nombre de lignes
  • sql_col_<N> — noms des colonnes
  • sql_<row>_<col> — valeurs
  • sql_result — première valeur
  • sql_affected_rows — lignes affectées pour DML
import groovy.sql.Sql
import java.util.regex.Pattern

// ============================================================
// Échantillonneur JSR223 JMeter - Exécuteur SQL Universel
// ============================================================
// Paramètres (séparés par des espaces) :
//   args[0] = URL JDBC      (jdbc:postgresql://host:5432/db)
//   args[1] = utilisateur BD
//   args[2] = mot de passe BD
//   args[3] = chemin vers fichier .sql (peut inclure des espaces)
//
// Les résultats sont écrits dans les vars JMeter :
//   sql_result_count    - nombre de lignes
//   sql_col_count       - nombre de colonnes
//   sql_col_<N>         - nom de colonne N (index 1)
//   sql_<row>_<col>     - valeur (index 1, ex. sql_1_1)
//   sql_result          - première valeur (raccourci scalaire)
// ============================================================

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

// --- Lire le SQL depuis le fichier ---
def rawSql = new File(sqlFile).getText("UTF-8")

// --- Substituer ${varName} -> vars.get("varName") ---
def resolved = rawSql.replaceAll(/\$\{(\w+)\}/) { fullMatch, varName ->
    def value = vars.get(varName)
    if (value == null) {
        log.warn("Variable '${varName}' non trouvée dans vars JMeter, laissée telle quelle")
        return fullMatch
    }
    return value
}

log.info("Exécution SQL :\n${resolved}")

// --- Connexion et exécution ---
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 - attendre un 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 a retourné 0 ligne")
            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 a retourné ${rows.size()} ligne(s), ${colNames.size()} colonne(s)")

    } 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 exécuté, lignes affectées : ${affected}")
    }

} catch (Exception e) {
    log.error("Échec exécution SQL : ${e.message}", e)
    throw e
} finally {
    sql.close()
}

Paramètres dans l'échantillonneur JSR223 : URL JDBC, utilisateur, mot de passe, chemin vers .sql. Les chemins relatifs fonctionnent en GUI et CLI si les répertoires de travail correspondent.

Scripts Groovy en dehors du JMX

Les éléments JSR223 sont déplacés dans des fichiers .groovy. Dans les paramètres, activez Mettre en cache le script compilé si disponible — sans cela, la compilation à chaque appel réduit les performances.

Google AdInline article slot

Les utilitaires communs (sql_executor.groovy, json_utils.groovy) vont dans un dossier common, accessible à tous les tests.

Modularité via Fragment de Test et Contrôleur Include

Les Groupes de Threads par domaine sont sauvegardés comme Fragments de Test (clic droit → Enregistrer comme Fragment de Test). Ajoutez-les au JMX principal via le Contrôleur Include.

Pièges :

Google AdInline article slot
  • En CLI, les chemins se résolvent depuis le répertoire de travail, pas le JMX
  • En CI/CD, définissez cd vers le répertoire du projet

Structure du projet après refactorisation

Avant :

project/
└── src/test/jmeter/
    └── test_plan.jmx            # 50 000+ lignes

Après :

project/
└── src/test/jmeter/
    ├── test_plan.jmx                        # ~5 000 lignes
    ├── common/
    │   ├── sql_executor.groovy
    │   └── json_utils.groovy
    ├── domaine_a/
    │   ├── domaine_a_fragment.jmx
    │   ├── creer_entite.sql
    │   ├── nettoyage.sql
    │   ├── pre_traitement.groovy
    │   └── validation_reponse.groovy
    ├── domaine_b/
    │   ├── domaine_b_fragment.jmx
    │   ├── preparer_donnees.sql
    │   └── verifier_resultat.groovy
    └── domaine_c/
        ├── domaine_c_fragment.jmx
        ├── initialisation.sql
        └── assertions.groovy

Le JMX contient seulement la séquence d'exécution. La logique est dans des fichiers spécialisés avec support IDE.

Points clés à retenir

  • Taille JMX réduite 10x : 50 000 → 5 000 lignes
  • SQL et Groovy hors JMX avec coloration et autocomplétion
  • Fragment de Test + Contrôleur Include pour la modularité
  • Script SQL universel avec substitution ${varName}
  • La mise en cache du script compilé est essentielle pour Groovy

— Editorial Team

Advertisement 728x90

Lire ensuite