¿Qué es Gatling?

Gatling es una herramienta de pruebas de carga open-source y de alto rendimiento diseñada para integración continua y flujos de trabajo orientados a desarrolladores. Construido sobre Scala con una arquitectura asíncrona no-bloqueante (Akka + Netty), Gatling puede simular miles de usuarios concurrentes con un consumo de memoria significativamente menor que herramientas basadas en hilos como JMeter.

Gatling produce reportes HTML detallados e interactivos de forma nativa — a menudo considerados los reportes de performance testing más atractivos de la industria. Estos reportes incluyen distribuciones de tiempo de respuesta, percentiles, conteo de solicitudes en el tiempo y análisis de errores.

Cuándo Elegir Gatling

Cada herramienta de pruebas de carga tiene sus fortalezas. Así se compara Gatling.

CaracterísticaGatlingJMeterk6
LenguajeDSL Scala/Java/KotlinGUI + XMLJavaScript
ArquitecturaAsync no-bloqueanteUn hilo por usuarioGoroutines de Go
ReportesHTML hermosos integradosBásicos, necesita pluginsTerminal + JSON/CSV
CI/CDPlugin Maven/Gradle/sbtModo CLICLI nativo
ProtocoloHTTP, WebSocket, JMS, MQTTHTTP, JDBC, FTP, LDAP, JMSHTTP, WebSocket, gRPC
Uso de recursosMuy bajoAltoBajo
Curva de aprendizajePronunciada (Scala)ModeradaFácil (JS)

Elige Gatling cuando: Necesitas reportes excelentes, tu equipo usa lenguajes JVM, quieres bajo consumo de recursos a escala, o necesitas integración nativa con Maven/Gradle para CI/CD.

Estructura de Simulación en Gatling

Una simulación de Gatling es una clase Scala que extiende Simulation. Contiene tres partes principales:

  1. Configuración de protocolo — Configuración HTTP, URL base, headers
  2. Definición de escenario — El recorrido del usuario (secuencia de solicitudes)
  3. Perfil de inyección — Cómo se agregan usuarios en el tiempo
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._

class BasicSimulation extends Simulation {

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

  // 2. Definición de escenario
  val scn = scenario("Explorar Productos")
    .exec(
      http("Listar Productos")
        .get("/api/products")
        .check(status.is(200))
        .check(jsonPath("$.products").exists)
    )
    .pause(2)
    .exec(
      http("Detalles del Producto")
        .get("/api/products/1")
        .check(status.is(200))
        .check(jsonPath("$.name").saveAs("productName"))
    )
    .pause(1)

  // 3. Setup con perfil de inyección
  setUp(
    scn.inject(
      rampUsers(100).during(60.seconds)
    )
  ).protocols(httpProtocol)
   .assertions(
     global.responseTime.percentile3.lt(1000),
     global.successfulRequests.percent.gt(99)
   )
}

Configuración de Protocolo

El builder del protocolo HTTP define configuraciones compartidas para todas las solicitudes:

val httpProtocol = http
  .baseUrl("https://api.example.com")
  .acceptHeader("application/json")
  .acceptEncodingHeader("gzip, deflate")
  .userAgentHeader("Gatling/3.9")
  .shareConnections

Definición de Escenario

Un escenario es una secuencia de acciones que un usuario virtual realiza:

val scn = scenario("Recorrido del Usuario")
  .exec(http("Página Principal").get("/"))
  .pause(1, 3)  // pausa aleatoria entre 1-3 segundos
  .exec(http("Login").post("/login")
    .body(StringBody("""{"user":"test","pass":"123"}"""))
    .check(jsonPath("$.token").saveAs("authToken"))
  )
  .exec(http("Dashboard").get("/dashboard")
    .header("Authorization", "Bearer ${authToken}")
  )

Acciones clave:

  • exec() — Ejecutar una solicitud HTTP
  • pause() — Esperar entre solicitudes (think time)
  • repeat() — Repetir una secuencia N veces
  • during() — Repetir durante una duración especificada
  • doIf() / doIfOrElse() — Ejecución condicional
  • feed() — Inyectar datos desde un feeder (CSV, JSON, JDBC)

Feeders (Parametrización de Datos)

Los feeders proporcionan datos de prueba a los usuarios virtuales:

// Feeder CSV
val csvFeeder = csv("users.csv").circular  // reciclar al agotarse

// Feeder JSON
val jsonFeeder = jsonFile("products.json").random

// Feeder en línea
val inlineFeeder = Iterator.continually(Map(
  "username" -> s"user_${scala.util.Random.nextInt(1000)}",
  "email" -> s"user${scala.util.Random.nextInt(1000)}@test.com"
))

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

Checks y Assertions

Checks validan respuestas individuales:

http("Obtener Usuario")
  .get("/api/users/1")
  .check(status.is(200))
  .check(jsonPath("$.name").is("John"))
  .check(responseTimeInMillis.lt(500))
  .check(jsonPath("$.id").saveAs("userId"))
  .check(header("Content-Type").is("application/json"))

Assertions definen criterios de aprobación/rechazo para toda la simulación:

setUp(scn.inject(...))
  .assertions(
    global.responseTime.max.lt(5000),
    global.responseTime.percentile3.lt(1000),
    global.successfulRequests.percent.gt(99),
    details("Login").responseTime.mean.lt(500)
  )

Perfiles de Inyección

Los perfiles de inyección definen cómo se introducen usuarios virtuales en la simulación. Gatling ofrece varias estrategias:

setUp(
  scn.inject(
    nothingFor(5.seconds),
    atOnceUsers(10),
    rampUsers(100).during(60.seconds),
    constantUsersPerSec(20).during(120.seconds),
    rampUsersPerSec(1).to(50).during(60.seconds)
  )
)

Estos perfiles se pueden combinar para crear patrones de carga complejos.

Ejecutar Gatling

# Usando el bundle
./bin/gatling.sh

# Usando Maven
mvn gatling:test

# Usando Gradle
gradle gatlingRun

# Usando sbt
sbt Gatling/test

Gatling genera un reporte HTML en target/gatling/ con gráficos de tiempos de respuesta, throughput y usuarios activos en el tiempo.

Ejercicio: Diseñar una Simulación en Gatling

Crea una simulación de Gatling para una plataforma de subastas en línea.

Escenario

La plataforma de subastas tiene estos endpoints:

EndpointMétodoDescripción
/api/auth/loginPOSTLogin de usuario
/api/auctionsGETListar subastas activas
/api/auctions/{id}GETDetalles de subasta
/api/auctions/{id}/bidPOSTRealizar una oferta

Requisitos

  1. Usa un feeder CSV para credenciales de usuario
  2. Encadena solicitudes: login, explorar subastas, ver detalles, hacer oferta
  3. Extrae el token de auth del login y el ID de subasta de la lista
  4. Agrega pausas entre acciones (1-3 segundos)
  5. Perfil de inyección: 50 usuarios en ramp-up de 2 minutos, mantener 3 minutos, bajar
  6. Assertions: p95 < 1000ms, tasa de éxito > 98%
Pista: Estructura de la Simulación
class AuctionSimulation extends Simulation {
  val httpProtocol = http.baseUrl("https://auction-api.example.com")
  val csvFeeder = csv("users.csv").circular

  val scn = scenario("Ofertas en Subasta")
    .feed(csvFeeder)
    .exec(/* login */)
    .pause(1, 3)
    .exec(/* listar subastas, guardar auctionId */)
    .pause(1, 2)
    .exec(/* ver detalles de subasta */)
    .pause(1, 2)
    .exec(/* hacer oferta */)

  setUp(
    scn.inject(
      rampUsers(50).during(120.seconds),
      nothingFor(180.seconds)
    )
  ).protocols(httpProtocol)
   .assertions(/* tus assertions */)
}
Solución: Simulación Completa de Gatling
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._

class AuctionSimulation extends Simulation {

  val httpProtocol = http
    .baseUrl("https://auction-api.example.com")
    .acceptHeader("application/json")
    .contentTypeHeader("application/json")

  val csvFeeder = csv("users.csv").circular

  val scn = scenario("Flujo de Ofertas en Subasta")
    .feed(csvFeeder)
    .exec(
      http("Login")
        .post("/api/auth/login")
        .body(StringBody("""{"username":"${username}","password":"${password}"}"""))
        .check(status.is(200))
        .check(jsonPath("$.token").saveAs("authToken"))
    )
    .pause(1, 3)
    .exec(
      http("Listar Subastas")
        .get("/api/auctions")
        .header("Authorization", "Bearer ${authToken}")
        .check(status.is(200))
        .check(jsonPath("$.auctions[0].id").saveAs("auctionId"))
        .check(jsonPath("$.auctions[0].currentBid").saveAs("currentBid"))
    )
    .pause(1, 2)
    .exec(
      http("Detalles de Subasta")
        .get("/api/auctions/${auctionId}")
        .header("Authorization", "Bearer ${authToken}")
        .check(status.is(200))
        .check(jsonPath("$.title").exists)
    )
    .pause(1, 2)
    .exec(session => {
      val currentBid = session("currentBid").as[String].toDouble
      val newBid = currentBid + 10.0
      session.set("newBid", newBid.toString)
    })
    .exec(
      http("Hacer Oferta")
        .post("/api/auctions/${auctionId}/bid")
        .header("Authorization", "Bearer ${authToken}")
        .body(StringBody("""{"amount":${newBid}}"""))
        .check(status.in(200, 201))
    )

  setUp(
    scn.inject(
      rampUsers(50).during(120.seconds),
      nothingFor(180.seconds)
    )
  ).protocols(httpProtocol)
   .assertions(
     global.responseTime.percentile(95).lt(1000),
     global.successfulRequests.percent.gt(98)
   )
}

Análisis del Reporte HTML:

  • Distribución de Tiempo de Respuesta: Verifica que el histograma se incline a la izquierda (respuestas rápidas)
  • Usuarios Activos en el Tiempo: Verifica que el patrón de ramp-up coincida con tu perfil de inyección
  • Solicitudes por Segundo: Busca estabilidad en throughput durante el estado estable
  • Percentiles de Tiempo de Respuesta: p95 debe mantenerse bajo 1000ms durante toda la prueba
  • Pestaña de Errores: Identifica qué solicitudes fallan y con cuántos usuarios

Tips Profesionales

  • DSL Java/Kotlin: Si tu equipo no conoce Scala, Gatling 3.7+ soporta DSLs en Java y Kotlin igualmente potentes. El DSL de Java es el punto de partida recomendado para la mayoría de los equipos.
  • Modelo Cerrado vs Abierto: Gatling soporta tanto modelo cerrado (número fijo de usuarios concurrentes) como modelo abierto (usuarios llegan a una tasa). Usa modelo abierto para aplicaciones web donde constantemente llegan nuevos usuarios independientemente de cuántos están activos.
  • Gatling Recorder: Similar al grabador de JMeter, Gatling incluye un convertidor HAR y grabador de proxy HTTP para capturar tráfico del navegador y convertirlo en scripts de Gatling.
  • Maven Archetype: Inicia nuevos proyectos con mvn archetype:generate -DarchetypeGroupId=io.gatling.highcharts -DarchetypeArtifactId=gatling-highcharts-maven-archetype para una estructura de proyecto preconfigurada.
  • Integración CI: Los plugins Maven/Gradle de Gatling facilitan ejecutar pruebas de carga en Jenkins, GitHub Actions o GitLab CI. Configura assertions para fallar el build si el rendimiento se degrada.