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
- JMeter Tutorial - Pruebas de carga basadas en GUI
- k6 Load Testing Guide - Performance testing basado en JavaScript
- API Testing Guide - Fundamentos de testing de REST API
- CI/CD Testing Integration - Pipelines de testing continuo
