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 становится более полезным для манипуляции сессиями и условных потоков.

Официальные ресурсы

Смотрите также