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.
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 lignessql_col_<N>— noms des colonnessql_<row>_<col>— valeurssql_result— première valeursql_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.
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 :
- En CLI, les chemins se résolvent depuis le répertoire de travail, pas le JMX
- En CI/CD, définissez
cdvers 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
Aucun commentaire pour le moment.