TL;DR
- Gatling использует Scala DSL для читаемых, поддерживаемых скриптов нагрузочного тестирования
- Асинхронная архитектура обрабатывает тысячи виртуальных пользователей с низким потреблением ресурсов
- Feeders внедряют тестовые данные, assertions валидируют требования к производительности
- Красивые HTML-отчёты генерируются автоматически после каждого запуска
- Code-based подход естественно интегрируется с CI/CD пайплайнами
Идеально для: Команд, желающих поддерживаемые нагрузочные тесты как код, сценариев с высокой конкурентностью Пропусти, если: Нет опыта кодирования (JMeter GUI может быть проще для старта) Время чтения: 15 минут
Твои JMeter тесты работают, но скрипты становятся неподдерживаемыми. XML-файлы невозможно ревьюить в pull requests. Запуск 10,000 виртуальных пользователей требует нескольких машин.
Gatling решает эту проблему. Тесты пишутся на Scala DSL — читаемый код, который живёт в твоём репозитории. Асинхронная архитектура симулирует тысячи пользователей на одной машине. Отчёты генерируются автоматически с детальными метриками.
Этот туториал покрывает Gatling от установки до интеграции с CI/CD — всё для высокопроизводительного нагрузочного тестирования.
Что такое Gatling?
Gatling — это open-source фреймворк для нагрузочного тестирования, построенный на Scala, Akka и Netty. Он использует асинхронную, неблокирующую архитектуру, которая эффективно симулирует массивные конкурентные нагрузки пользователей.
Почему Gatling:
- Высокая производительность — асинхронная модель обрабатывает 10,000+ пользователей на машину
- Код как тесты — Scala DSL интегрируется с системами контроля версий и CI/CD
- Красивые отчёты — детальные HTML-отчёты с графиками и перцентилями
- Дружественный к разработчикам — поддержка IDE, отладка, переиспользование кода
- Эффективное использование ресурсов — меньше CPU/памяти чем у thread-based инструментов
Установка
Предварительные требования
# Требуется Java 11+
java -version
# Скачать Gatling
# Вариант 1: Скачать с https://gatling.io/open-source/
# Вариант 2: 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>
Структура проекта
project/
├── src/
│ └── test/
│ ├── scala/
│ │ └── simulations/
│ │ └── BasicSimulation.scala
│ └── resources/
│ ├── gatling.conf
│ ├── data/
│ │ └── users.csv
│ └── bodies/
│ └── request.json
├── pom.xml
└── target/
└── gatling/
└── results/
Первая симуляция
Базовая структура
package simulations
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
class BasicSimulation extends Simulation {
// HTTP конфигурация
val httpProtocol = http
.baseUrl("https://api.example.com")
.acceptHeader("application/json")
.contentTypeHeader("application/json")
// Определение сценария
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)
)
// Профиль нагрузки
setUp(
scn.inject(atOnceUsers(10))
).protocols(httpProtocol)
}
Запуск тестов
# Запуск всех симуляций
mvn gatling:test
# Запуск конкретной симуляции
mvn gatling:test -Dgatling.simulationClass=simulations.BasicSimulation
# С Gatling bundle
./bin/gatling.sh
HTTP-запросы
Типы запросов
class HttpExamplesSimulation extends Simulation {
val httpProtocol = http.baseUrl("https://api.example.com")
val scn = scenario("HTTP Examples")
// GET запрос
.exec(
http("Get Request")
.get("/users")
.queryParam("page", "1")
.queryParam("limit", "10")
)
// POST с JSON телом
.exec(
http("Create User")
.post("/users")
.body(StringBody("""{"name":"John","email":"john@example.com"}"""))
.asJson
)
// POST с телом из файла
.exec(
http("Create from File")
.post("/users")
.body(RawFileBody("bodies/user.json"))
.asJson
)
// PUT запрос
.exec(
http("Update User")
.put("/users/1")
.body(StringBody("""{"name":"Updated Name"}"""))
.asJson
)
// DELETE запрос
.exec(
http("Delete User")
.delete("/users/1")
)
setUp(scn.inject(atOnceUsers(1))).protocols(httpProtocol)
}
Заголовки и аутентификация
val httpProtocol = http
.baseUrl("https://api.example.com")
.acceptHeader("application/json")
.contentTypeHeader("application/json")
.authorizationHeader("Bearer ${token}")
.userAgentHeader("Gatling/3.10")
// Или для конкретного запроса
.exec(
http("Authenticated Request")
.get("/protected")
.header("Authorization", "Bearer ${token}")
.header("X-Custom-Header", "value")
)
Сессия и переменные
Сохранение данных из ответа
val scn = scenario("Session Example")
// Сохранить значение из ответа
.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"))
)
// Использовать сохранённые значения
.exec(
http("Get Profile")
.get("/users/${userId}")
.header("Authorization", "Bearer ${authToken}")
)
// Отладка: вывод сессии
.exec { session =>
println(s"Token: ${session("authToken").as[String]}")
println(s"User ID: ${session("userId").as[String]}")
session
}
Функции сессии
.exec { session =>
// Модифицировать сессию
session.set("customVar", "value")
}
.exec { session =>
// Условная логика
val userId = session("userId").as[String]
if (userId.toInt > 100) {
session.set("userType", "premium")
} else {
session.set("userType", "standard")
}
}
Feeders (тестовые данные)
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
)
Стратегии Feeder
// Последовательный - каждый пользователь получает следующую строку
val seqFeeder = csv("data/users.csv").queue
// Циклический - возвращается к началу
val circularFeeder = csv("data/users.csv").circular
// Случайный - случайная строка каждый раз
val randomFeeder = csv("data/users.csv").random
// Перемешанный - случайный, но каждая строка используется один раз
val shuffleFeeder = csv("data/users.csv").shuffle
// JSON feeder
val jsonFeeder = jsonFile("data/users.json").circular
// Кастомный feeder
val customFeeder = Iterator.continually(Map(
"email" -> s"user${scala.util.Random.nextInt(1000)}@example.com",
"timestamp" -> System.currentTimeMillis()
))
Проверки и Assertions
Проверки ответа
.exec(
http("Get Users")
.get("/users")
// Проверка статуса
.check(status.is(200))
// Проверка времени ответа
.check(responseTimeInMillis.lt(2000))
// Проверка заголовка
.check(header("Content-Type").is("application/json"))
// 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(bodyString.exists)
.check(substring("success").exists)
)
Глобальные Assertions
setUp(
scn.inject(rampUsers(100).during(60.seconds))
).protocols(httpProtocol)
.assertions(
// Assertions времени ответа
global.responseTime.max.lt(5000),
global.responseTime.percentile(95).lt(2000),
global.responseTime.mean.lt(1000),
// Процент успеха
global.successfulRequests.percent.gt(99),
// Запросов в секунду
global.requestsPerSec.gt(100),
// Конкретный запрос
details("Login").responseTime.max.lt(3000),
details("Login").failedRequests.percent.lt(1)
)
Профили нагрузки
Паттерны инъекции
setUp(
// Фиксированное количество пользователей сразу
scn.inject(atOnceUsers(100)),
// Нарастание за время
scn.inject(rampUsers(100).during(60.seconds)),
// Постоянная скорость
scn.inject(constantUsersPerSec(10).during(5.minutes)),
// Нарастающая скорость
scn.inject(
rampUsersPerSec(1).to(100).during(2.minutes)
),
// Ступенчатая нагрузка
scn.inject(
incrementUsersPerSec(10)
.times(5)
.eachLevelLasting(30.seconds)
.separatedByRampsLasting(10.seconds)
.startingFrom(10)
),
// Комплексный профиль
scn.inject(
nothingFor(5.seconds),
atOnceUsers(10),
rampUsers(50).during(30.seconds),
constantUsersPerSec(20).during(2.minutes),
rampUsersPerSec(20).to(50).during(1.minute)
)
)
Множественные сценарии
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)
Управление потоком сценария
Паузы
val scn = scenario("With Pauses")
.exec(http("Request 1").get("/api/1"))
.pause(1.second) // Фиксированная пауза
.exec(http("Request 2").get("/api/2"))
.pause(1.second, 3.seconds) // Случайная между 1-3с
.exec(http("Request 3").get("/api/3"))
.pause(normalPausesWithStdDevDuration(2.seconds, 500.millis)) // Нормальное распределение
Циклы и условия
val scn = scenario("Flow Control")
// Повторить фиксированное количество раз
.repeat(5) {
exec(http("Repeated").get("/api/data"))
}
// Повторить со счётчиком
.repeat(3, "counter") {
exec(http("Item ${counter}").get("/api/items/${counter}"))
}
// Цикл в течение времени
.during(30.seconds) {
exec(http("Looped").get("/api/status"))
.pause(1.second)
}
// Условное выполнение
.doIf("${userType.equals('premium')}") {
exec(http("Premium Feature").get("/api/premium"))
}
// Случайный переключатель
.randomSwitch(
60.0 -> exec(http("Path A").get("/api/a")),
40.0 -> exec(http("Path B").get("/api/b"))
)
Обработка ошибок
val scn = scenario("Error Handling")
.exec(
http("Might Fail")
.get("/api/unreliable")
.check(status.is(200))
)
.exitHereIfFailed // Остановить пользователя если предыдущее упало
.exec(http("After Success").get("/api/next"))
// Try-catch стиль
.tryMax(3) {
exec(http("Retry Request").get("/api/flaky"))
}
// Выход из блока при ошибке
.exitBlockOnFail {
exec(http("Critical").get("/api/critical"))
exec(http("Dependent").get("/api/dependent"))
}
Отчёты
HTML-отчёт
Gatling генерирует детальные HTML-отчёты автоматически:
target/gatling/basicsimulation-20260128120000/
├── index.html # Главный отчёт
├── js/
├── style/
└── simulation.log
Секции отчёта:
- Global Information — всего запросов, процент успеха/неудач
- Response Time Distribution — гистограмма времени ответа
- Response Time Percentiles — 50th, 75th, 95th, 99th перцентили во времени
- Requests per Second — график пропускной способности
- Responses per Second — скорость ответов сервера
- Active Users — конкурентные пользователи во время теста
Интеграция с CI/CD
GitHub Actions
name: Load Tests
on:
schedule:
- cron: '0 2 * * *' # Ежедневно в 2 часа ночи
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 с помощью ИИ
ИИ-инструменты могут помочь писать и оптимизировать симуляции Gatling.
Что ИИ делает хорошо:
- Генерация сценариев из API-документации
- Создание реалистичных данных для feeders
- Предложение подходящих профилей нагрузки
- Конвертация других форматов в Gatling DSL
Что всё ещё требует людей:
- Определение реалистичных паттернов поведения пользователей
- Установка осмысленных порогов производительности
- Анализ результатов в бизнес-контексте
- Решения по планированию мощностей
FAQ
Что такое Gatling?
Gatling — это Scala-based open-source инструмент для нагрузочного тестирования, спроектированный для высокой производительности и дружественных к разработчикам воркфлоу. Он использует асинхронную, неблокирующую архитектуру, построенную на Akka и Netty, которая эффективно симулирует тысячи конкурентных пользователей с низким потреблением ресурсов. Тесты пишутся как код с использованием читаемого Scala DSL.
Gatling бесплатный?
Да, Gatling Open Source полностью бесплатен под лицензией Apache 2.0. Он включает полный движок тестирования, Scala DSL и HTML-отчётность. Gatling Enterprise (ранее Gatling FrontLine) — платный продукт, который добавляет распределённое тестирование, мониторинг в реальном времени, продвинутую аналитику и функции командной работы для организаций, нуждающихся в дополнительном масштабе и возможностях управления.
Gatling vs JMeter — что лучше?
Gatling превосходит в сценариях с высокой конкурентностью благодаря более низкому потреблению ресурсов из-за асинхронной архитектуры — один инстанс Gatling может симулировать столько же пользователей, сколько 3-4 инстанса JMeter. Скрипты Gatling — это код, делающий их поддерживаемыми и дружественными к CI/CD. JMeter имеет GUI, который проще для начинающих, и большую экосистему плагинов. Выбирай Gatling для performance-тестирования, ведомого разработчиками; выбирай JMeter для команд, предпочитающих визуальное создание тестов.
Нужно ли знать Scala для Gatling?
Базовые знания Scala помогут, но не обязательны для продуктивной работы. DSL Gatling спроектирован для читаемости, и большинство сценариев используют простые цепочки методов вроде .get(), .check(), .saveAs(). Ты можешь писать эффективные нагрузочные тесты в течение нескольких часов после начала. Для сложных сценариев с кастомной логикой знание Scala становится более полезным для манипуляции сессиями и условных потоков.
Официальные ресурсы
Смотрите также
- JMeter Tutorial - GUI-based нагрузочное тестирование
- k6 Load Testing Guide - JavaScript-based performance testing
- API Testing Guide - Основы тестирования REST API
- CI/CD Testing Integration - Пайплайны непрерывного тестирования
