Gatling (как обсуждается в K6: Modern Load Testing with JavaScript for DevOps Teams) — это мощный, высокопроизводительный инструмент нагрузочного тестирования, разработанный для тестирования веб-приложений, API и микросервисов. Построенный на Akka и Netty, Gatling (как обсуждается в Load Testing with JMeter: Complete Guide) превосходно симулирует тысячи одновременных пользователей с минимальным потреблением ресурсов, предоставляя детальные, действенные метрики производительности через красивые HTML-отчёты.
Почему Gatling для Performance Тестирования?
Gatling выделяется на ландшафте performance тестирования благодаря уникальной комбинации дружественного разработчикам DSL, исключительной производительности и enterprise-grade функций:
- Scala DSL: Выразительный, type-safe скриптинг с поддержкой IDE
- Высокая производительность: Async non-blocking архитектура справляется с массивной конкурентностью
- Реалистичные симуляции: Продвинутое моделирование пользователей с think times и pacing
- Богатые протоколы: Поддержка HTTP, WebSocket, SSE, JMS out of the box
- Красивые отчёты: Детальные HTML-отчёты с графиками и метриками
- Готов к CI/CD: Выполнение из командной строки и интеграция Maven/Gradle
Gatling vs Другие Инструменты Load Testing
Функция | Gatling | JMeter | K6 | Locust |
---|---|---|---|---|
Scripting | Scala DSL | GUI/XML | JavaScript | Python |
Производительность | Отличная | Средняя | Отличная | Средняя |
Использование Ресурсов | Низкое | Высокое | Низкое | Среднее |
Поддержка Протоколов | HTTP, WS, JMS | Обширная | HTTP/2, WS, gRPC | HTTP |
Reporting | Отличный | Базовый | Хороший | Базовый |
Recorder | Да | Да | Нет | Нет |
Интеграция CI/CD | Нативная | Плагины | Нативная | Ручная |
Начало Работы с Gatling
Установка с Maven
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>gatling-tests</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<gatling.version>3.10.3</gatling.version>
<gatling-maven-plugin.version>4.7.0</gatling-maven-plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>io.gatling.highcharts</groupId>
<artifactId>gatling-charts-highcharts</artifactId>
<version>${gatling.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.gatling</groupId>
<artifactId>gatling-maven-plugin</artifactId>
<version>${gatling-maven-plugin.version}</version>
</plugin>
</plugins>
</build>
</project>
Установка с Gradle
plugins {
id 'scala'
id 'io.gatling.gradle' version '3.10.3.1'
}
repositories {
mavenCentral()
}
dependencies {
gatling 'io.gatling.highcharts:gatling-charts-highcharts:3.10.3'
}
gatling {
simulations = {
include '**/*Simulation.scala'
}
}
Первая Симуляция Gatling
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")
.userAgentHeader("Gatling Performance Test")
// Определение сценария
val scn = scenario("Basic Load Test")
.exec(
http("Get Users")
.get("/users")
.check(status.is(200))
)
.pause(1)
.exec(
http("Get User Details")
.get("/users/1")
.check(status.is(200))
.check(jsonPath("$.name").exists)
)
// Инъекция нагрузки
setUp(
scn.inject(
atOnceUsers(10), // 10 пользователей сразу
rampUsers(50) during (30.seconds) // 50 пользователей за 30 секунд
)
).protocols(httpProtocol)
}
Запустить симуляцию:
mvn gatling:test
# или
./gradlew gatlingRun
Профили Инъекции Нагрузки
Модели Открытой vs Закрытой Нагрузки
// Открытая Модель: Новые пользователи прибывают независимо от производительности системы
setUp(
scn.inject(
nothingFor(4.seconds), // Ждать 4 секунды
atOnceUsers(10), // Инжектировать 10 пользователей сразу
rampUsers(50) during (30.seconds), // Подъём до 50 пользователей
constantUsersPerSec(20) during (1.minute), // Постоянная частота
rampUsersPerSec(10) to 50 during (2.minutes) // Подъём частоты
)
)
// Закрытая Модель: Фиксированное количество одновременных пользователей
setUp(
scn.inject(
constantConcurrentUsers(10) during (30.seconds),
rampConcurrentUsers(10) to 50 during (1.minute)
)
)
Продвинутые Паттерны Инъекции
setUp(
// Паттерн stress test
scn.inject(
rampUsers(10) during (1.minute), // Прогрев
constantUsersPerSec(100) during (5.minutes), // Устойчивая нагрузка
rampUsersPerSec(100) to 500 during (2.minutes), // Подъём до пика
constantUsersPerSec(500) during (5.minutes), // Пиковая нагрузка
rampUsersPerSec(500) to 100 during (2.minutes) // Охлаждение
),
// Spike test
scn.inject(
constantUsersPerSec(50) during (2.minutes),
atOnceUsers(500), // Внезапный spike
constantUsersPerSec(50) during (2.minutes)
)
).protocols(httpProtocol)
Throttling
setUp(
scn.inject(rampUsers(1000) during (1.minute))
).protocols(httpProtocol)
.throttle(
reachRps(100) in (10.seconds), // Достичь 100 RPS
holdFor(1.minute), // Удержать
jumpToRps(50), // Спуститься до 50
holdFor(2.minutes)
)
Конфигурация HTTP Протокола
Полная Настройка HTTP Протокола
val httpProtocol = http
.baseUrl("https://api.example.com")
.acceptHeader("application/json, text/html, */*")
.acceptEncodingHeader("gzip, deflate")
.acceptLanguageHeader("en-US,en;q=0.5")
.userAgentHeader("Gatling/3.10")
.shareConnections // Connection pooling
.disableWarmUp // Пропустить warmup запросы
// Headers
.header("X-API-Version", "v1")
// Аутентификация
.authorizationHeader("Bearer ${token}")
// Proxy
.proxy(Proxy("proxy.example.com", 8080)
.credentials("username", "password"))
// Timeouts
.requestTimeout(30.seconds)
.readTimeout(30.seconds)
// Keep-alive
.connectionHeader("keep-alive")
// Автоматическая обработка редиректов
.disableFollowRedirect
Построение Сложных Сценариев
Композиция Сценариев
// Переиспользуемые компоненты сценария
val browse = exec(
http("Homepage")
.get("/")
.check(status.is(200))
).pause(2, 5) // Случайная пауза между 2-5 секунд
val search = exec(
http("Search Products")
.get("/search")
.queryParam("q", "laptop")
.check(status.is(200))
.check(jsonPath("$.results[*].id").findAll.saveAs("productIds"))
).pause(1, 3)
val viewProduct = exec(
http("View Product")
.get("/products/${productId}")
.check(status.is(200))
).pause(3, 7)
// Скомпоновать в полный сценарий
val scn = scenario("E-commerce User Journey")
.exec(browse)
.exec(search)
.exec(session => {
val productIds = session("productIds").as[Vector[String]]
val randomId = productIds(scala.util.Random.nextInt(productIds.length))
session.set("productId", randomId)
})
.exec(viewProduct)
Условное Выполнение
val scn = scenario("Условный Поток")
.exec(
http("Get User")
.get("/users/me")
.check(status.is(200))
.check(jsonPath("$.role").saveAs("userRole"))
)
.doIf(session => session("userRole").as[String] == "admin") {
exec(
http("Admin Dashboard")
.get("/admin/dashboard")
.check(status.is(200))
)
}
.doIfOrElse(session => session("userRole").as[String] == "premium") {
exec(http("Premium Features").get("/premium"))
} {
exec(http("Standard Features").get("/standard"))
}
Циклы и Итерации
val scn = scenario("Пример Цикла")
// Повторить фиксированное количество раз
.repeat(5) {
exec(http("Request ${counter}").get("/api/data"))
.pause(1)
}
// Повторять пока условие истинно
.asLongAs(session => session("continue").as[Boolean]) {
exec(http("Poll").get("/api/status"))
.pause(5)
}
// Повторять в течение времени
.during(30.seconds) {
exec(http("Sustained Load").get("/api/endpoint"))
.pause(1)
}
// Итерировать по коллекции
.foreach(List("product1", "product2", "product3"), "productId") {
exec(
http("Get Product ${productId}")
.get("/products/${productId}")
)
}
Построение Запросов и Валидация
POST Запросы с Body
val scn = scenario("CRUD Операции API")
// JSON body из строки
.exec(
http("Create User")
.post("/users")
.body(StringBody("""{"name": "John", "email": "john@example.com"}"""))
.asJson
.check(status.is(201))
.check(jsonPath("$.id").saveAs("userId"))
)
// JSON body из шаблона
.exec(
http("Create Order")
.post("/orders")
.body(ElFileBody("templates/order.json"))
.asJson
)
// Form data
.exec(
http("Submit Form")
.post("/submit")
.formParam("field1", "value1")
.formParam("field2", "${dynamicValue}")
)
Продвинутые Checks
val scn = scenario("Валидация Ответа")
.exec(
http("Комплексная Валидация")
.get("/api/products")
.check(status.is(200))
// JSON path checks
.check(jsonPath("$.products[*]").count.gte(10))
.check(jsonPath("$.products[0].name").exists)
.check(jsonPath("$.products[*].price").findAll.saveAs("prices"))
// Response time check
.check(responseTimeInMillis.lte(500))
// Header checks
.check(header("Content-Type").is("application/json"))
// Body substring check
.check(substring("success").exists)
// Регулярное выражение
.check(regex("""id":\s*(\d+)""").findAll.saveAs("ids"))
// Проверка MD5/SHA hash
.check(md5.is("expected-hash"))
)
Управление Сессией
val scn = scenario("Переменные Сессии")
.exec { session =>
println(s"Session ID: ${session.userId}")
session.set("customVar", "value")
}
.exec(
http("Login")
.post("/login")
.body(StringBody("""{"username": "${username}", "password": "${password}"}"""))
.check(jsonPath("$.token").saveAs("authToken"))
)
.exec(
http("Authenticated Request")
.get("/protected")
.header("Authorization", "Bearer ${authToken}")
)
// Доступ к данным сессии
.exec { session =>
val token = session("authToken").as[String]
println(s"Using token: $token")
session
}
Feeders для Data-Driven Тестов
CSV Feeder
// users.csv:
// username,password,role
// user1,pass1,admin
// user2,pass2,user
val csvFeeder = csv("users.csv").random // Случайный выбор
val csvFeederCircular = csv("users.csv").circular // Round-robin
val csvFeederQueue = csv("users.csv").queue // Последовательный, один раз
val scn = scenario("Data-Driven Login")
.feed(csvFeeder)
.exec(
http("Login as ${username}")
.post("/login")
.body(StringBody("""{"username": "${username}", "password": "${password}"}"""))
.check(status.is(200))
)
JSON и Кастомные Feeders
// JSON feeder
val jsonFeeder = jsonFile("products.json").random
// Array feeder
val customFeeder = Array(
Map("product" -> "Laptop", "price" -> "999"),
Map("product" -> "Phone", "price" -> "599")
).random
// Генерируемый feeder
val idFeeder = Iterator.continually(
Map("userId" -> scala.util.Random.nextInt(1000))
)
val scn = scenario("Custom Feeder")
.feed(idFeeder)
.exec(
http("Get User ${userId}")
.get("/users/${userId}")
)
Реальные Сценарии Тестирования
Полный Пользовательский Поток E-commerce
package simulations
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
class EcommerceSimulation extends Simulation {
val httpProtocol = http
.baseUrl("https://ecommerce.example.com")
.acceptHeader("application/json")
val csvFeeder = csv("users.csv").circular
// Сценарий: Просмотр и Покупка
val browse = scenario("Browse Products")
.feed(csvFeeder)
.exec(
http("Homepage")
.get("/")
.check(status.is(200))
)
.pause(2, 4)
.exec(
http("Category Page")
.get("/category/electronics")
.check(status.is(200))
.check(jsonPath("$.products[*].id").findAll.saveAs("productIds"))
)
.pause(3, 6)
val purchase = scenario("Complete Purchase")
.feed(csvFeeder)
.exec(browse)
.exec { session =>
val productIds = session("productIds").as[Vector[String]]
session.set("selectedProduct", productIds.head)
}
.exec(
http("Product Details")
.get("/products/${selectedProduct}")
.check(status.is(200))
)
.pause(5, 10)
.exec(
http("Add to Cart")
.post("/cart")
.body(StringBody("""{"productId": "${selectedProduct}", "quantity": 1}"""))
.asJson
.check(status.is(200))
)
.pause(2, 5)
.exec(
http("Checkout")
.post("/checkout")
.body(ElFileBody("checkout.json"))
.asJson
.check(status.is(201))
)
// Симуляция нагрузки
setUp(
browse.inject(
rampUsers(100) during (2.minutes)
),
purchase.inject(
rampUsers(50) during (3.minutes)
)
).protocols(httpProtocol)
.assertions(
global.responseTime.percentile3.lt(1500),
global.successfulRequests.percent.gt(95)
)
}
Интеграция CI/CD
Jenkins Pipeline
pipeline {
agent any
stages {
stage('Checkout') {
steps {
git 'https://github.com/your/repo.git'
}
}
stage('Run Gatling Tests') {
steps {
sh 'mvn gatling:test'
}
}
stage('Publish Reports') {
steps {
gatlingArchive()
}
}
}
post {
always {
publishHTML([
reportDir: 'target/gatling',
reportFiles: 'index.html',
reportName: 'Gatling Report'
])
}
}
}
GitHub Actions
name: Performance Tests
on:
schedule:
- cron: '0 2 * * *'
workflow_dispatch:
jobs:
gatling:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Run Gatling Tests
run: mvn gatling:test
- name: Upload Gatling Results
uses: actions/upload-artifact@v3
if: always()
with:
name: gatling-results
path: target/gatling/
Assertions и SLAs
setUp(
scn.inject(rampUsers(100) during (1.minute))
).protocols(httpProtocol)
.assertions(
// Глобальные assertions
global.responseTime.max.lt(5000),
global.responseTime.mean.lt(1000),
global.responseTime.percentile3.lt(1500), // 95-й перцентиль
global.responseTime.percentile4.lt(2000), // 99-й перцентиль
// Показатель успеха
global.successfulRequests.percent.gt(99),
global.failedRequests.count.lt(10),
// Assertions специфичные для запроса
forAll.responseTime.max.lt(3000),
details("Homepage").responseTime.max.lt(500),
details("API Call").successfulRequests.percent.is(100)
)
Лучшие Практики
Организация Симуляций
// BaseSimulation.scala - Общая конфигурация
abstract class BaseSimulation extends Simulation {
val httpProtocol = http
.baseUrl(System.getProperty("baseUrl", "https://api.example.com"))
.acceptHeader("application/json")
.userAgentHeader("Gatling")
val defaultPause = 1.second
}
// Специфичные симуляции расширяют базовую
class UserSimulation extends BaseSimulation {
// Код специфичный для симуляции
}
Think Time и Pacing
val scn = scenario("Реалистичный Пользователь")
.exec(http("Action 1").get("/page1"))
.pause(2, 5) // Случайно между 2-5 секунд
.exec(http("Action 2").get("/page2"))
.pace(10.seconds) // Поддерживать 10с между итерациями
.exec(http("Action 3").get("/page3"))
.rendezVous(100) // Ждать пока 100 пользователей достигнут этой точки
Заключение
Gatling сочетает высокую производительность с продуктивностью разработчика, что делает его отличным выбором для современного performance тестирования. Его Scala DSL обеспечивает type safety и выразительность, в то время как его async архитектура предоставляет исключительные возможности генерации нагрузки.
Ключевые сильные стороны:
- Высокопроизводительная async архитектура
- Выразительный Scala DSL с поддержкой IDE
- Красивые, детальные HTML-отчёты
- Отличная поддержка протоколов
- Сильная интеграция CI/CD
- (как обсуждается в Artillery Performance Testing: Modern Load Testing with YAML Scenarios) Enterprise-ready функции
Независимо от того, тестируете ли вы REST API, микросервисы или полные веб-приложения, Gatling предоставляет производительность, функции и отчётность, необходимые для комплексного нагрузочного тестирования в современных DevOps средах.