TL;DR

  • Gatling usa Scala DSL para scripts de pruebas de carga legibles y mantenibles
  • Arquitectura asíncrona maneja miles de usuarios virtuales con bajo uso de recursos
  • Feeders inyectan datos de prueba, assertions validan requisitos de rendimiento
  • Reportes HTML hermosos generados automáticamente después de cada ejecución
  • Enfoque basado en código se integra naturalmente con pipelines CI/CD

Ideal para: Equipos que quieren pruebas de carga mantenibles como código, escenarios de alta concurrencia Omite si: Sin experiencia en código (JMeter GUI puede ser más fácil para empezar) Tiempo de lectura: 15 minutos

Tus tests de JMeter funcionan pero los scripts se están volviendo inmantenibles. Los archivos XML son imposibles de revisar en pull requests. Ejecutar 10,000 usuarios virtuales requiere múltiples máquinas.

Gatling resuelve esto. Los tests se escriben en Scala DSL — código legible que vive en tu repositorio. La arquitectura asíncrona simula miles de usuarios en una sola máquina. Los reportes se generan automáticamente con métricas detalladas.

Este tutorial cubre Gatling desde instalación hasta integración CI/CD — todo para pruebas de carga de alto rendimiento.

¿Qué es Gatling?

Gatling es un framework open-source para pruebas de carga construido sobre Scala, Akka y Netty. Usa una arquitectura asíncrona y no bloqueante que simula eficientemente cargas masivas de usuarios concurrentes.

Por qué Gatling:

  • Alto rendimiento — modelo asíncrono maneja 10,000+ usuarios por máquina
  • Código como tests — Scala DSL se integra con control de versiones y CI/CD
  • Reportes hermosos — reportes HTML detallados con gráficos y percentiles
  • Amigable para desarrolladores — soporte de IDE, debugging, reutilización de código
  • Eficiente en recursos — menor CPU/memoria que herramientas basadas en threads

Instalación

Prerrequisitos

# Se requiere Java 11+
java -version

# Descargar Gatling
# Opción 1: Descargar desde https://gatling.io/open-source/
# Opción 2: Dependencia Maven/Gradle

Maven Setup

<dependencies>
    <dependency>
        <groupId>io.gatling.highcharts</groupId>
        <artifactId>gatling-charts-highcharts</artifactId>
        <version>3.10.3</version>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>io.gatling</groupId>
            <artifactId>gatling-maven-plugin</artifactId>
            <version>4.7.0</version>
        </plugin>
    </plugins>
</build>

Estructura del Proyecto

project/
├── src/
│   └── test/
│       ├── scala/
│       │   └── simulations/
│       │       └── BasicSimulation.scala
│       └── resources/
│           ├── gatling.conf
│           ├── data/
│           │   └── users.csv
│           └── bodies/
│               └── request.json
├── pom.xml
└── target/
    └── gatling/
        └── results/

Primera Simulación

Estructura Básica

package simulations

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._

class BasicSimulation extends Simulation {

  // Configuración HTTP
  val httpProtocol = http
    .baseUrl("https://api.example.com")
    .acceptHeader("application/json")
    .contentTypeHeader("application/json")

  // Definición del Escenario
  val scn = scenario("Basic API Test")
    .exec(
      http("Get Users")
        .get("/users")
        .check(status.is(200))
    )
    .pause(1.second)
    .exec(
      http("Get User Details")
        .get("/users/1")
        .check(status.is(200))
        .check(jsonPath("$.name").exists)
    )

  // Perfil de Carga
  setUp(
    scn.inject(atOnceUsers(10))
  ).protocols(httpProtocol)
}

Ejecutando Tests

# Ejecutar todas las simulaciones
mvn gatling:test

# Ejecutar simulación específica
mvn gatling:test -Dgatling.simulationClass=simulations.BasicSimulation

# Con Gatling bundle
./bin/gatling.sh

Requests HTTP

Tipos de Request

class HttpExamplesSimulation extends Simulation {

  val httpProtocol = http.baseUrl("https://api.example.com")

  val scn = scenario("HTTP Examples")
    // Request GET
    .exec(
      http("Get Request")
        .get("/users")
        .queryParam("page", "1")
        .queryParam("limit", "10")
    )
    // POST con cuerpo JSON
    .exec(
      http("Create User")
        .post("/users")
        .body(StringBody("""{"name":"John","email":"john@example.com"}"""))
        .asJson
    )
    // POST con cuerpo desde archivo
    .exec(
      http("Create from File")
        .post("/users")
        .body(RawFileBody("bodies/user.json"))
        .asJson
    )
    // Request PUT
    .exec(
      http("Update User")
        .put("/users/1")
        .body(StringBody("""{"name":"Updated Name"}"""))
        .asJson
    )
    // Request DELETE
    .exec(
      http("Delete User")
        .delete("/users/1")
    )

  setUp(scn.inject(atOnceUsers(1))).protocols(httpProtocol)
}

Headers y Autenticación

val httpProtocol = http
  .baseUrl("https://api.example.com")
  .acceptHeader("application/json")
  .contentTypeHeader("application/json")
  .authorizationHeader("Bearer ${token}")
  .userAgentHeader("Gatling/3.10")

// O por request
.exec(
  http("Authenticated Request")
    .get("/protected")
    .header("Authorization", "Bearer ${token}")
    .header("X-Custom-Header", "value")
)

Sesión y Variables

Guardando Datos de Respuesta

val scn = scenario("Session Example")
  // Guardar valor de respuesta
  .exec(
    http("Login")
      .post("/auth/login")
      .body(StringBody("""{"email":"user@example.com","password":"pass123"}"""))
      .asJson
      .check(jsonPath("$.token").saveAs("authToken"))
      .check(jsonPath("$.user.id").saveAs("userId"))
  )
  // Usar valores guardados
  .exec(
    http("Get Profile")
      .get("/users/${userId}")
      .header("Authorization", "Bearer ${authToken}")
  )
  // Debug: imprimir sesión
  .exec { session =>
    println(s"Token: ${session("authToken").as[String]}")
    println(s"User ID: ${session("userId").as[String]}")
    session
  }

Funciones de Sesión

.exec { session =>
  // Modificar sesión
  session.set("customVar", "value")
}

.exec { session =>
  // Lógica condicional
  val userId = session("userId").as[String]
  if (userId.toInt > 100) {
    session.set("userType", "premium")
  } else {
    session.set("userType", "standard")
  }
}

Feeders (Datos de Test)

CSV Feeder

# data/users.csv
username,password,role
john@example.com,pass123,admin
jane@example.com,pass456,user
bob@example.com,pass789,user
val csvFeeder = csv("data/users.csv").random

val scn = scenario("Login Test")
  .feed(csvFeeder)
  .exec(
    http("Login")
      .post("/auth/login")
      .body(StringBody("""{"email":"${username}","password":"${password}"}"""))
      .asJson
  )

Estrategias de Feeder

// Secuencial - cada usuario obtiene la siguiente fila
val seqFeeder = csv("data/users.csv").queue

// Circular - vuelve al inicio
val circularFeeder = csv("data/users.csv").circular

// Aleatorio - fila aleatoria cada vez
val randomFeeder = csv("data/users.csv").random

// Mezclado - aleatorio pero cada fila se usa una vez
val shuffleFeeder = csv("data/users.csv").shuffle

// JSON feeder
val jsonFeeder = jsonFile("data/users.json").circular

// Feeder personalizado
val customFeeder = Iterator.continually(Map(
  "email" -> s"user${scala.util.Random.nextInt(1000)}@example.com",
  "timestamp" -> System.currentTimeMillis()
))

Checks y Assertions

Checks de Respuesta

.exec(
  http("Get Users")
    .get("/users")
    // Check de estado
    .check(status.is(200))
    // Check de tiempo de respuesta
    .check(responseTimeInMillis.lt(2000))
    // Check de header
    .check(header("Content-Type").is("application/json"))
    // Checks JSON
    .check(jsonPath("$").exists)
    .check(jsonPath("$.data").ofType[Seq[Any]])
    .check(jsonPath("$.data[*].id").findAll.saveAs("userIds"))
    .check(jsonPath("$.meta.total").ofType[Int].gt(0))
    // Check de cuerpo como string
    .check(bodyString.exists)
    .check(substring("success").exists)
)

Assertions Globales

setUp(
  scn.inject(rampUsers(100).during(60.seconds))
).protocols(httpProtocol)
  .assertions(
    // Assertions de tiempo de respuesta
    global.responseTime.max.lt(5000),
    global.responseTime.percentile(95).lt(2000),
    global.responseTime.mean.lt(1000),
    // Tasa de éxito
    global.successfulRequests.percent.gt(99),
    // Requests por segundo
    global.requestsPerSec.gt(100),
    // Request específico
    details("Login").responseTime.max.lt(3000),
    details("Login").failedRequests.percent.lt(1)
  )

Perfiles de Carga

Patrones de Inyección

setUp(
  // Usuarios fijos de una vez
  scn.inject(atOnceUsers(100)),

  // Rampa a lo largo del tiempo
  scn.inject(rampUsers(100).during(60.seconds)),

  // Tasa constante
  scn.inject(constantUsersPerSec(10).during(5.minutes)),

  // Tasa con rampa
  scn.inject(
    rampUsersPerSec(1).to(100).during(2.minutes)
  ),

  // Carga escalonada
  scn.inject(
    incrementUsersPerSec(10)
      .times(5)
      .eachLevelLasting(30.seconds)
      .separatedByRampsLasting(10.seconds)
      .startingFrom(10)
  ),

  // Perfil complejo
  scn.inject(
    nothingFor(5.seconds),
    atOnceUsers(10),
    rampUsers(50).during(30.seconds),
    constantUsersPerSec(20).during(2.minutes),
    rampUsersPerSec(20).to(50).during(1.minute)
  )
)

Múltiples Escenarios

val loginScn = scenario("Login Flow")
  .exec(/* login steps */)

val browseScn = scenario("Browse Products")
  .exec(/* browse steps */)

val checkoutScn = scenario("Checkout")
  .exec(/* checkout steps */)

setUp(
  loginScn.inject(rampUsers(100).during(1.minute)),
  browseScn.inject(rampUsers(200).during(1.minute)),
  checkoutScn.inject(rampUsers(50).during(1.minute))
).protocols(httpProtocol)

Control de Flujo del Escenario

Pausas

val scn = scenario("With Pauses")
  .exec(http("Request 1").get("/api/1"))
  .pause(1.second)                     // Pausa fija
  .exec(http("Request 2").get("/api/2"))
  .pause(1.second, 3.seconds)          // Aleatoria entre 1-3s
  .exec(http("Request 3").get("/api/3"))
  .pause(normalPausesWithStdDevDuration(2.seconds, 500.millis)) // Distribución normal

Bucles y Condiciones

val scn = scenario("Flow Control")
  // Repetir cantidad fija
  .repeat(5) {
    exec(http("Repeated").get("/api/data"))
  }
  // Repetir con contador
  .repeat(3, "counter") {
    exec(http("Item ${counter}").get("/api/items/${counter}"))
  }
  // Bucle durante tiempo
  .during(30.seconds) {
    exec(http("Looped").get("/api/status"))
      .pause(1.second)
  }
  // Ejecución condicional
  .doIf("${userType.equals('premium')}") {
    exec(http("Premium Feature").get("/api/premium"))
  }
  // Switch aleatorio
  .randomSwitch(
    60.0 -> exec(http("Path A").get("/api/a")),
    40.0 -> exec(http("Path B").get("/api/b"))
  )

Manejo de Errores

val scn = scenario("Error Handling")
  .exec(
    http("Might Fail")
      .get("/api/unreliable")
      .check(status.is(200))
  )
  .exitHereIfFailed  // Detener usuario si el anterior falló
  .exec(http("After Success").get("/api/next"))

// Estilo try-catch
.tryMax(3) {
  exec(http("Retry Request").get("/api/flaky"))
}

// Salir de bloque en falla
.exitBlockOnFail {
  exec(http("Critical").get("/api/critical"))
  exec(http("Dependent").get("/api/dependent"))
}

Reportes

Reporte HTML

Gatling genera reportes HTML detallados automáticamente:

target/gatling/basicsimulation-20260128120000/
├── index.html              # Reporte principal
├── js/
├── style/
└── simulation.log

Secciones del reporte:

  • Global Information — total de requests, tasas de éxito/falla
  • Response Time Distribution — histograma de tiempos de respuesta
  • Response Time Percentiles — percentiles 50th, 75th, 95th, 99th en el tiempo
  • Requests per Second — gráfico de throughput
  • Responses per Second — tasa de respuesta del servidor
  • Active Users — usuarios concurrentes durante el test

Integración CI/CD

GitHub Actions

name: Load Tests

on:
  schedule:
    - cron: '0 2 * * *'  # Diario a las 2 AM
  workflow_dispatch:

jobs:
  gatling:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Run Gatling tests
        run: mvn gatling:test -Dgatling.simulationClass=simulations.LoadTest

      - name: Upload report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: gatling-report
          path: target/gatling/*/

      - name: Fail on assertion errors
        run: |
          if grep -q "KO" target/gatling/*/simulation.log; then
            echo "Performance assertions failed!"
            exit 1
          fi

Gatling con Asistencia de IA

Las herramientas de IA pueden ayudar a escribir y optimizar simulaciones de Gatling.

Lo que la IA hace bien:

  • Generar escenarios desde documentación de API
  • Crear datos realistas para feeders
  • Sugerir perfiles de carga apropiados
  • Convertir otros formatos a Gatling DSL

Lo que aún necesita humanos:

  • Definir patrones de comportamiento de usuario realistas
  • Establecer umbrales de rendimiento significativos
  • Analizar resultados en contexto de negocio
  • Decisiones de planificación de capacidad

FAQ

¿Qué es Gatling?

Gatling es una herramienta de pruebas de carga open-source basada en Scala diseñada para alto rendimiento y workflows amigables para desarrolladores. Usa una arquitectura asíncrona y no bloqueante construida sobre Akka y Netty que simula eficientemente miles de usuarios concurrentes con bajo consumo de recursos. Los tests se escriben como código usando un DSL Scala legible.

¿Gatling es gratis?

Sí, Gatling Open Source es completamente gratuito bajo la licencia Apache 2.0. Incluye el motor de testing completo, Scala DSL y reportería HTML. Gatling Enterprise (anteriormente Gatling FrontLine) es un producto pago que añade testing distribuido, monitoreo en tiempo real, analíticas avanzadas y funciones de colaboración de equipo para organizaciones que necesitan escala adicional y capacidades de gestión.

¿Gatling vs JMeter — cuál es mejor?

Gatling sobresale en escenarios de alta concurrencia con menor uso de recursos debido a su arquitectura asíncrona — una instancia de Gatling puede simular tantos usuarios como 3-4 instancias de JMeter. Los scripts de Gatling son código, haciéndolos mantenibles y amigables con CI/CD. JMeter tiene una GUI que es más fácil para principiantes y un ecosistema más grande de plugins. Elige Gatling para testing de rendimiento liderado por desarrolladores; elige JMeter para equipos que prefieren creación visual de tests.

¿Necesito saber Scala para Gatling?

Conocimientos básicos de Scala ayudan pero no son requeridos para ser productivo. El DSL de Gatling está diseñado para ser legible y la mayoría de escenarios usan cadenas de métodos simples como .get(), .check(), .saveAs(). Puedes escribir pruebas de carga efectivas en pocas horas de empezar. Para escenarios complejos con lógica personalizada, el conocimiento de Scala se vuelve más útil para manipulación de sesiones y flujos condicionales.

Recursos Oficiales

Ver También