1. Введение

В этой статье мы рассмотрим, как можно использовать Gatling для тестирования производительности любой конечной точки Rest, уделяя особое внимание нагрузочному тестированию. Мы начнем с краткого введения в различные типы тестирования производительности и их ключевые показатели эффективности (KPI).

Далее вы получите общее представление о терминологии Gatling. Мы разберем пример с использованием плагина Maven Gatling и зависимостей, а также изучим Gatling Java DSL для проведения нагрузочного тестирования с имитацией сценария.

Наконец, запустим имитацию и посмотрим на сгенерированный отчет.

2. Типы тестирования производительности

Тестирование производительности включает в себя измерение различных метрик, с целью проверить производительность системы при различных уровнях трафика и пропускной способности. Другие типы тестирования производительности — это нагрузочное тестирование, стресс-тестирование, тестирование стабильности (soak testing), тестирование пиковых нагрузок (spike testing) и тестирование масштабируемости (scalability testing). Давайте кратко рассмотрим назначение каждого типа стратегии тестирования производительности.

Нагрузочное тестирование предполагает тестирование системы под большой нагрузкой в течение определенного периода времени. С другой стороны, стресс-тестирование предполагает постепенное увеличение нагрузки на систему с целью найти точку ее предельную точку. Во время тестирования стабильности через систему пропускается стабильный трафик в течение длительного времени, чтобы выявить узкие места. Как видим из названия, тестирование пиковых нагрузок (Spike testing) заключается в проверке производительности системы, когда количество запросов быстро возрастает до уровня перегрузки, а после этого снова снижается. Наконец, тестирование масштабируемости представляет собой проверку производительности системы при увеличении или уменьшении числа пользовательских запросов.

При проведении тестирования производительности можно собрать несколько ключевых показателей эффективности для измерения производительности системы. К ним относятся: время отклика транзакций, пропускная способность (количество транзакций, обработанных за определенный период) и ошибки (например, тайм-ауты). Стресс-тестирование также может помочь выявить утечки памяти, замедления, уязвимости безопасности и повреждения данных.

В этой статье мы сосредоточимся на нагрузочном тестировании с помощью Gatling.

3. Ключевые термины

Давайте начнем с базовых терминов, используемых в контексте работы с фреймворком Gatling.

  • Сценарий: серия шагов, которые выполняют виртуальные пользователи для воспроизведения распространённых действий пользователя, таких как вход в систему или покупка.

  • Фидеры: механизм, позволяющий вводить тестовые данные из внешних источников, таких как файлы CSV или JSON, в действия виртуальных пользователей.

  • Имитация: определяет количество виртуальных пользователей, которые выполняют сценарий в течение определенного периода времени.

  • Сессия: каждый виртуальный пользователь поддерживается сессией, которая отслеживает сообщения, которыми обмениваются во время сценария.

  • Recorder: пользовательский интерфейс Gatling предоставляет инструмент Recorder, который генерирует сценарий и имитацию.

4. Пример настройки

Давайте сосредоточимся на небольшой части микросервиса Employee management, состоящей из RestController с конечными точками POST и GET, которые нужно протестированы на нагрузку.

Прежде чем приступить к реализации нашего простого решения, давайте добавим необходимые зависимости Gatling:

dependency>
    <groupId>io.gatling</groupId>
    <artifactId>gatling-app</artifactId>
    <version>3.7.2</version>
</dependency>
Copy
<dependency>
&lt;groupId&gt;io.gatling.highcharts&lt;/groupId&gt;
&lt;artifactId&gt;gatling-charts-highcharts&lt;/artifactId&gt;
&lt;version&gt;<span class="hljs-number" style="box-sizing: border-box; color: rgb(78, 147, 89);">3.7</span><span class="hljs-number" style="box-sizing: border-box; color: rgb(78, 147, 89);">.2</span>&lt;/version&gt;

</dependency>

Далее, добавим плагин maven:

<plugin>
    <groupId>io.gatling</groupId>
    <artifactId>gatling-maven-plugin</artifactId>
    <version>4.2.9</version>
    <configuration>
        <simulationClass>org.baeldung.EmployeeRegistrationSimulation</simulationClass>
    </configuration>
</plugin>

Как следует из информации в файле pom.xml, мы явно задали класс имитации EmployeeRegistationSimulation в конфигурации плагина. Это означает, что плагин будет использовать указанный класс в качестве основы для запуска имитаций.

Далее, давайте определим RestController с конечными точками POST и GET, которые мы хотим протестировать с помощью Gatling:

@PostMapping(consumes = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<Void> addEmployee(@RequestBody EmployeeCreationRequest request, UriComponentsBuilder uriComponentsBuilder) {
    URI location = uriComponentsBuilder.path("/api/employees/{id}")
      .buildAndExpand("99")
      .toUri();
    return ResponseEntity.created(location)
      .build();
}

Далее добавим конечную точку GET:

@GetMapping("/{id}")
public Employee getEmployeeWithId(@PathVariable("id") Long id) {
    List<Employee> allEmployees = createEmployees();
    return allEmployees.get(ThreadLocalRandom.current()
      .nextInt(0, allEmployees.size()));
}

Теперь рассмотрим класс Simulation и входящие в него компоненты и API, которые позволяют проводить нагрузочное тестирование.

5. Шаги имитации

Имитация представляет собой нагрузочный тест, который отражает различные аспекты, например, каким образом могут работать несколько групп пользователей, какие сценарии они будут выполнять и как будут вводиться новые виртуальные пользователи. Во фреймворке Gatling класс Simulation является первичным компонентом, который инициирует процесс нагрузочного тестирования. Java API Gatling включает в себя изменяемый абстрактный класс Simulation. Мы можем расширить класс Simulation с учетом конкретных требований, чтобы создать пользовательскую имитацию:

Public class EmployeeRegistrationSimulation extends Simulation {

    private static final HttpProtocolBuilder HTTP_PROTOCOL_BUILDER = setupProtocolForSimulation();

    private static final Iterator<Map<String, Object>> FEED_DATA = setupTestFeedData();

    private static final ScenarioBuilder POST_SCENARIO_BUILDER = buildPostScenario();

    // ...
}

По сути, здесь нам нужно определить следующее:

  •  Конфигурация протокола HTTP

  •  Заголовки

  •  Фидеры

  •  HTTP-запросы

  •  Сценарий

  •  Шаблон Load Injection

Теперь давайте рассмотрим отдельные шаги и то, как мы можем определить их с помощью DSL, предоставляемого Gatling. Начнем с конфигурации протокола.

5.1. Конфигурация протокола HTTP

Gatling — это инструмент нагрузочного тестирования, не зависящий от технологии. Он поддерживает различные протоколы, включая HTTP, HTTPS и WebSockets. В этом разделе мы сосредоточимся на настройке HTTP-протокола для сценария нагрузочного тестирования.

Для настройки деталей протокола HTTP в классе EmployeeRegistrationSimulation мы будем использовать тип HTTP DSL, который служит точкой входа для Gatling HTTP DSL. Затем мы воспользуемся DSL HTTPProtocolBuilder для определения конфигурации протокола HTTP:

private static HttpProtocolBuilder setupProtocolForSimulation() {
    return HttpDsl.http.baseUrl("http://localhost:8080")
      .acceptHeader("application/json")
      .maxConnectionsPerHost(10)
      .userAgentHeader("Gatling/Performance Test");
}

Настройка протокола HTTP в Gatling включает использование класса HttpDsl для определения конфигурации HTTP-протокола с помощью DSL HTTPProtocolBuilder. Ключевые параметры конфигурации включают baseUrl, acceptHeader, maxConnectionsPerHost и userAgentHeader. Эти параметры помогают гарантировать, что наш нагрузочный тест точно имитирует реальные сценарии.

5.2. Определение фидеров

Фидеры — это удобный API, который позволяет тестировщикам вводить данные из внешних источников в виртуальные сессии пользователей. Gatling поддерживает различные фидеры, такие как CSV, JSON, фидеры на основе файлов и фидеры на основе массивов/списков.

Далее давайте создадим метод, который будет возвращать тестовые данные для тест-кейса:

private static Iterator<Map<String, Object>> feedData() {
    Faker faker = new Faker();
    Iterator<Map<String, Object>> iterator;
    iterator = Stream.generate(() -> {
          Map<String, Object> stringObjectMap = new HashMap<>();
          stringObjectMap.put("empName", faker.name()
            .fullName());
          return stringObjectMap;
      })
      .iterator();
    return iterator;
}

Здесь создадим метод, возвращающий Iterator<Map<String, Object>> для получения тестовых данных для нашего тест-кейса. Затем метод feedData() генерирует тестовые данные с помощью библиотеки Faker, создает HashMap для хранения данных и возвращает итератор по этим данным.

По сути, фидер является псевдонимом типа для компонента Iterator<Map<String, Object>>, создаваемого методом feed. Метод feed опрашивает записи Map<String, Object> и вводит их содержимое в сценарий имитации.

Gatling предлагает несколько стратегий для встроенных фидеров, такие как queue(), random(), shuffle() и circular(). Кроме того, в зависимости от тестируемой системы, можно настроить механизм загрузки данных как eager() или batch().

5.3. Определение сценария

Сценарий в Gatling представляет собой типичное поведение пользователя, которому будут следовать виртуальные пользователи. Это рабочий процесс, основанный на ресурсах, определенных в контроллере Employee Controller. В данном случае мы создадим сценарий, имитирующий создание сотрудника с помощью простого рабочего процесса:

private static ScenarioBuilder buildPostScenario() {
    return CoreDsl.scenario("Load Test Creating Employee")
      .feed(FEED_DATA)
      .exec(http("create-employee-request").post("/api/employees")
        .header("Content-Type," "application/json")
        .body(StringBody("{ \"empName\": \"${empName}\" }"))
        .check(status().is(201))
        .check(header("Location").saveAs("location")))
      .exec(http("get-employee-request").get(session -> session.getString("location"))
        .check(status().is(200)));
    }

Gatling API предоставляет метод scenario(String name), который возвращает экземпляр класса ScenarioBuilder. ScenarioBuilder содержит детали сценария, включая источник тестовых данных и детали HTTP-запроса, такие как тело запроса, заголовки и ожидаемый код состояния.

По сути, мы создаем сценарий, в котором сначала отправляем запрос с помощью метода post для создания сотрудника. В теле запроса содержится значение empName (в формате JSON), полученное из тестовых данных. Метод также проверяет наличие ожидаемого кода состояния HTTP (201) и сохраняет значение заголовка Location в сессии с помощью метода saveAs.

Во втором запросе используется метод get для получения созданного сотрудника путем отправки сохраненного значения заголовка Location в URL запроса. Он также проверяет наличие ожидаемого кода состояния HTTP (200).

5.4. Модель инъекции нагрузки

В дополнение к определению сценария и протокола нужно определить модель инъекции нагрузки для имитации. В нашем примере мы будем постепенно увеличивать нагрузку, добавляя виртуальных пользователей с течением времени. Gatling предоставляет две модели инъекции нагрузки: открытую и закрытую. Открытая модель позволяет нам контролировать скорость прибытия виртуальных пользователей, что больше соответствует реальным системам.

Java API Gatling предоставляет класс OpenInjectionStep, который инкапсулирует общие атрибуты и поведение моделей нагрузки с открытой инъекцией. Существует три подкласса OpenInjectionStep, которые можно использовать:

  • ConstantRateOpenInjection: эта модель инъекций поддерживает постоянную скорость прибытия виртуальных пользователей.

  • RampRateOpenInjection: эта модель инъекции постепенно увеличивает скорость прибытия виртуальных пользователей.

  • Композитная: эта модель инъекций позволяет комбинировать различные типы моделей инъекций.

Для нашего примера воспользуемся RampRateOpenInjection. Мы начнем с 50 виртуальных пользователей и будем постепенно увеличивать нагрузку, добавляя по 50 пользователей каждые 30 секунд, пока не достигнем 200 виртуальных пользователей. Затем мы будем поддерживать нагрузку на уровне 200 виртуальных пользователей в течение 5 минут:

private RampRateOpenInjectionStep postEndpointInjectionProfile() {
    int totalDesiredUserCount = 200;
    double userRampUpPerInterval = 50;
    double rampUpIntervalSeconds = 30;
    int totalRampUptimeSeconds = 120;
    int steadyStateDurationSeconds = 300;

    return rampUsersPerSec(userRampUpPerInterval / (rampUpIntervalSeconds / 60)).to(totalDesiredUserCount)
      .during(Duration.ofSeconds(totalRampUptimeSeconds + steadyStateDurationSeconds));
}

Определив схему инъекции нагрузки, мы сможем точно смоделировать, как система будет вести себя при различных уровнях нагрузки. Это поможет выявить узкие места в производительности и убедиться, что наша система сможет справиться с ожидаемой пользовательской нагрузкой.

5.5. Настройка имитатора

Чтобы настроить имитацию, мы объединим протокол, сценарий и модель инъекции нагрузки, которые мы определили ранее. Эта настройка будет вызываться из конструктора класса EmployeeRegistrationSimulation:

public EmployeeRegistrationSimulation() {

    setUp(BUILD_POST_SCENARIO.injectOpen(postEndpointInjectionProfile())
      .protocols(HTTP_PROTOCOL_BUILDER));
}

5.6. Утверждения (Assertions)

Наконец, мы используем Gatling DSL для утверждения того, что имитация работает так, как ожидалось. Давайте вернемся к конструктору EmployeeRegistrationSimulation() и добавим несколько ассертов к существующему методу setup(...):

setUp(BUILD_POST_SCENARIO.injectOpen(postEndpointInjectionProfile())
  .protocols(HTTP_PROTOCOL_BUILDER))
  .assertions(
    global().responseTime().max().lte(10000),
    global().successfulRequests().percent().gt(90d)

Как мы видим, здесь мы хотим утвердить следующие условия:

  • Максимальное время отклика в соответствии с настройками должно быть меньше или равно 10 секундам.

  • Процент успешных запросов должен превышать 90.

6. Запуск имитации и анализ отчета

Когда мы создавали наш проект Gatling, мы использовали Maven с плагином Gatling Maven Plugin. Поэтому мы можем использовать задачу Maven для выполнения нашего теста Gatling. Чтобы запустить сценарий Gatling через Maven, откройте командную строку в папке проекта Gatling:

mvn gatling:test

В результате мы получим следующие метрики:

Наконец, Gatling генерирует HTML-отчет в каталоге target/gatling. Главный файл в этом каталоге — index.html, в котором кратко описана конфигурация нагрузочного теста, график распределения времени ответа и статистика по каждому запросу, как было описано выше. Давайте посмотрим на некоторые графики из отчета:

График запросов в секунду помогает нам понять, как система справляется с растущим уровнем трафика. Анализируя график запросов в секунду, мы можем определить оптимальное количество запросов, с которым система может работать без снижения производительности или возникновения ошибок. Эту информацию можно использовать, чтобы улучшить масштабируемость системы и чтобы она смогла справляться с ожидаемым уровнем трафика.

Еще один интересный график, на который стоит обратить внимание, — это диапазоны времени отклика:

Диаграмма распределения времени ответа показывает процент запросов, которые попадают в определенные группы по времени ответа, например, менее 800 миллисекунд, от 800 миллисекунд до 1,2 секунды и более 1,2 секунды. Мы видим, что все ответы лежат в пределах <800 мс.

Давайте посмотрим на процентили времени отклика:

Раздел статистики запросов показывает подробную информацию о каждом запросе, включая количество запросов, процент успешных запросов и различные процентили времени ответа, такие как 50-й, 75-й, 95-й и 99-й процентили. В целом, файл index.html предоставляет исчерпывающую сводку результатов нагрузочного тестирования, что позволяет легко определить узкие места и проблемы производительности.

7. Заключение

В этой статье мы научились использовать Gatling Java DSL для нагрузочного тестирования любой конечной точки REST.

Во-первых, мы провели краткий обзор различных типов тестирования производительности. Затем ввели ключевые термины, характерные для Gatling. Мы продемонстрировали, как реализовать нагрузочный тест на конечной точке POST, соблюдая при этом желаемую нагрузку и временные ограничения. Кроме того, мы можем проанализировать результаты тестирования, чтобы определить области для улучшения и оптимизации.

Полный код этой статьи можно найти на GitHub.


Какие стенды можно использовать для нагрузочного тестирования и каковы их особенности? Обсудим это на открытом уроке, который пройдет уже завтра вечером. Приглашаем всех желающих! Записаться на занятие можно на странице курса «Нагрузочное тестирование».

Комментарии (0)