Volver al inicio

Descomposición JMX JMeter: de 50k a 5k líneas

El artículo describe la refactorización de un archivo JMX monolítico en JMeter con 50 000 líneas. Extraer SQL a .sql con sustitución de variables, Groovy a archivos, modularidad vía Test Fragment. Resultado: tamaño 10 veces menor, revisión de código conveniente.

JMeter JMX monolito a módulos: refactorización 10x
Advertisement 728x90

Descomposición de Pruebas JMeter: De 50.000 a 5.000 Líneas en JMX

Los archivos JMX monolíticos en JMeter con más de 50.000 líneas convierten las revisiones de código, la edición y el mantenimiento en una pesadilla. El archivo no cabe en memoria, los conflictos de fusión durante el trabajo en paralelo son inevitables, y un diff XML de 3.000 líneas en una PR es ilegible. Los scripts JSR223 Groovy y SQL incrustados en JMX carecen de resaltado de sintaxis en el IDE.

La descomposición redujo el JMX a 5.000 líneas, trasladando la lógica de prueba a archivos separados. Probar un endpoint se redujo de 900 a 300 líneas. La estructura del proyecto se volvió modular, como el código regular.

Extracción de Scripts SQL en Archivos Separados

JDBC Request almacena SQL dentro de JMX sin resaltado. La función __FileToString lee archivos .sql pero no expande las variables de JMeter ${varName} de vars.

Google AdInline article slot

La solución es un Sampler JSR223 universal en Groovy. El script sustituye variables, ejecuta SELECT/DML y guarda resultados en vars:

  • sql_result_count — número de filas
  • sql_col_<N> — nombres de columnas
  • sql_<fila>_<col> — valores
  • sql_result — primer valor
  • sql_affected_rows — filas afectadas para DML
import groovy.sql.Sql
import java.util.regex.Pattern

// ============================================================
// JMeter JSR223 Sampler - Ejecutor SQL Universal
// ============================================================
// Parámetros (separados por espacios):
//   args[0] = URL JDBC      (jdbc:postgresql://host:5432/db)
//   args[1] = usuario DB
//   args[2] = contraseña DB
//   args[3] = ruta al archivo .sql (puede incluir espacios)
//
// Los resultados se escriben en vars de JMeter:
//   sql_result_count    - conteo de filas
//   sql_col_count       - conteo de columnas
//   sql_col_<N>         - nombre de columna N (base 1)
//   sql_<fila>_<col>     - valor (base 1, ej., sql_1_1)
//   sql_result          - primer valor (atajo escalar)
// ============================================================

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

// --- Leer SQL del archivo ---
def rawSql = new File(sqlFile).getText("UTF-8")

// --- Sustituir ${varName} -> vars.get("varName") ---
def resolved = rawSql.replaceAll(/\$\{(\w+)\}/) { fullMatch, varName ->
    def value = vars.get(varName)
    if (value == null) {
        log.warn("Variable '${varName}' no encontrada en vars de JMeter, dejando como está")
        return fullMatch
    }
    return value
}

log.info("Ejecutando SQL:\n${resolved}")

// --- Conectar y ejecutar ---
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 - esperar 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 devolvió 0 filas")
            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 devolvió ${rows.size()} fila(s), ${colNames.size()} col(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 ejecutado, filas afectadas: ${affected}")
    }

} catch (Exception e) {
    log.error("Ejecución SQL fallida: ${e.message}", e)
    throw e
} finally {
    sql.close()
}

Parámetros en JSR223 Sampler: URL JDBC, usuario, contraseña, ruta a .sql. Las rutas relativas funcionan en GUI y CLI si los directorios de trabajo coinciden.

Scripts Groovy Fuera de JMX

Los elementos JSR223 se trasladan a archivos .groovy. En configuraciones, habilita Cache compiled script if available — sin esto, la compilación en cada llamada reduce el rendimiento.

Google AdInline article slot

Utilidades comunes (sql_executor.groovy, json_utils.groovy) van en una carpeta common, accesible para todas las pruebas.

Modularidad mediante Fragmento de Prueba e Include Controller

Los Thread Groups por dominio se guardan como Fragmentos de Prueba (clic derecho → Save as Test Fragment). Se añaden al JMX principal mediante Include Controller.

Trampas:

Google AdInline article slot
  • En CLI, las rutas se resuelven desde el directorio de trabajo, no desde el JMX
  • En CI/CD, establece cd al directorio del proyecto

Estructura del Proyecto Después de la Refactorización

Antes:

proyecto/
└── src/test/jmeter/
    └── plan_de_prueba.jmx            # 50.000+ líneas

Después:

proyecto/
└── src/test/jmeter/
    ├── plan_de_prueba.jmx                        # ~5.000 líneas
    ├── common/
    │   ├── sql_executor.groovy
    │   └── json_utils.groovy
    ├── dominio_a/
    │   ├── fragmento_dominio_a.jmx
    │   ├── crear_entidad.sql
    │   ├── limpieza.sql
    │   ├── pre_procesamiento.groovy
    │   └── validacion_respuesta.groovy
    ├── dominio_b/
    │   ├── fragmento_dominio_b.jmx
    │   ├── preparar_datos.sql
    │   └── verificar_resultado.groovy
    └── dominio_c/
        ├── fragmento_dominio_c.jmx
        ├── inicializacion.sql
        └── aserciones.groovy

JMX contiene solo la secuencia de ejecución. La lógica está en archivos especializados con soporte de IDE.

Conclusiones Clave

  • Tamaño JMX reducido 10x: 50.000 → 5.000 líneas
  • SQL y Groovy fuera de JMX con resaltado y autocompletado
  • Fragmento de Prueba + Include Controller para modularidad
  • Script SQL universal con sustitución de ${varName}
  • Cache compiled script es esencial para Groovy

— Editorial Team

Advertisement 728x90

Leer después