Нагрузочное тестирование с Gatling — Полное руководство (Часть 2)

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Перевод материала подготовлен в преддверии старта онлайн-курса «Нагрузочное тестирование».


4. Тестируемое приложение — база данных видеоигр

В оставшейся части этого руководства мы будем писать тесты для базы данных видеоигр (Video Game Database). Это приложение представляет собой, как вы наверное догадались, выдуманную базу данных видеоигр. Оно может похвастаться простым API, задокументированным с помощью Swagger, которое покрывает все HTTP-команды (Get, Put, Update, Delete) и поддерживает XML и JSON пейлоады.

Я рекомендую вам клонировать базу данных видеоигр и запускать ее локально. Для этого сначала клонируйте (или загрузите) репозиторий и откройте терминал в том месте, где вы сохранили его на своем компьютере. Оттуда вы можете запустить приложение с помощью:

  • Gradle./gradlew bootRun

  • Mavenmvn spring-boot:run

Через несколько секунд приложение загрузится, и вы сможете перейти к нему по этому адресу: http://localhost:8080/swagger-ui/index.html#/

Здесь должны увидеть главную страницу Swagger для базы данных видеоигр, которая должна выглядеть как-то так:

Video Game Database
Video Game Database

Советую вам немного потыкать API через Swagger, пробуя различные вызовы с XML и JSON.

Примечание: Если вы не хотите (или не можете) загрузить и запускать приложение локально, я также разместил версию базы данных видеоигр на AWS. Причина, по которой я настоятельно рекомендую запускать приложение локально, заключается в том, что любой, у кого есть этот AWS адрес, может манипулировать данными в базе данных, поэтому вы скорее всего увидите данные, отличные от тех, которые показаны в этом руководстве!

Теперь, когда у нас есть доступ к базе данных видеоигр, мы можем приступить к написанию скриптов Gatling.


5. Основы написания скриптов Gatling

В оставшейся части этого учебного руководства мы будем писать скрипты для нагрузочного тестирования на Gatling с целью изучить некоторые из основных концепций их разработки. Давайте начнем с создания нового пакета внутри папки scala нашего проекта, в котором будут храниться ваши Gatling скрипты. Назовите пакет simulations.

5.1 Базовая структура скрипта  Gatling

Внутри пакета создайте новый класс Scala с именем MyFirstTest. Добавьте туда следующий код:

package simulations

import io.gatling.core.Predef._
import io.gatling.http.Predef._

class MyFirstTest extends Simulation {
  
  // 1 Http Conf (конфигурация Http) 
  val httpConf = http.baseUrl("http://localhost:8080/app/")
    .header("Accept", "application/json")
    .proxy(Proxy("localhost", 8888))
  
  // 2 Scenario Definition (определение сценария)
  val scn = scenario("My First Test")
    .exec(http("Get All Games")
      .get("videogames"))
  
  // 3 Load Scenario (сценарий нагрузки)
  setUp(
    scn.inject(atOnceUsers(1))
  ).protocols(httpConf)
}

По сути это простейший скрипт Gatling, который мы только можем написать. Он выполняет один единственный вызов конечной точки по адресу http://localhost:8080/app/videogames.

Давайте обсудим каждую часть скрипта по порядку:

5.1.1 Операторы импорта

Мы добавили два этих оператора импорта в верхнюю часть скрипта:

import io.gatling.core.Predef._
import io.gatling.http.Predef._

Собственно здесь импортируются пакеты Gatling - они оба необходимы для всех скриптов Gatling.

5.1.2 Extends Simulation

Затем мы видим, что наш класс Scala расширяет класс Gatling Simulation:

class MyFirstTest extends Simulation {

Опять же, чтобы создать скрипт Gatling, мы всегда должны расширяться от класса Simulation пакета Gatling.

Перейдем непосредственно к коду внутри класса, который разделен на 3 отдельные секции:

5.1.3 Конфигурация HTTP (Http Conf)

Первое, что мы делаем, это настраиваем конфигурацию HTTP для нашего скрипта Gatling.

Конфигурация HTTP в нашем классе выглядит следующим образом:

// 1 Http Conf
val httpConf = http.baseUrl("http://localhost:8080/app/")
    .header("Accept", "application/json")

Здесь мы определяем baseUrl, который будет использоваться во всех наших последующих вызовах API в скрипте.

Мы также устанавливаем в заголовок по умолчанию (header), который будет отправляться при каждом вызове, Accept -> application/json.

В конфигурации HTTP можно определить и другие элементы - полный список смотрите в документации Gatling по конфигурации HTTP.

5.1.4 Определение сценария (Scenario Definition)

Секция определения сценария это то место в нашем скрипте Gatling, где мы определяем наш пользовательский путь (user journey). Это шаги, которые пользователь будет выполнять при взаимодействии с нашим приложением, например:

  • Перейти на эту страницу

  • Подождать 5 секунд

  • Затем перейти на другую страницу

  • Затем сформировать POST-запрос с некоторыми данными с помощью этой формы

  • и т. д.

Все определение нашего сценария в этом скрипте будет состоять из одного GET вызова конечной точки videogames. Обратите внимание, что вызывается полная конечная точка http://localhost:8080/app/videogames, так как мы определили baseUrl в конфигурации HTTP выше:

// 2 Scenario Definition (определение сценария)
val scn = scenario("My First Test")
          .exec(http("Get All Games")
              .get("videogames"))

5.1.5 - Сценарий нагрузки

Третья и последняя часть скрипта Gatling — это сценарий нагрузки. Здесь мы устанавливаем профиль нагрузки (например, количество виртуальных пользователей, как долго нужно проработать и т. д.) для нашего теста Gatling. Каждый из виртуальных пользователей будет выполнять сценарий, который мы определили во второй части, описанной выше. В этом примере мы создаем всего одного пользователя с одной итерацией

// 3 Load Scenario (сценарий нагрузки)
setUp(
   scn.inject(atOnceUsers(1))
   ).protocols(httpConf)

Вот у нас и получился достаточно примитивный скрипт Gatling! Мы можем запустить его, запустив класс Engine и выбрав скрипт MyFirstTest:

My First Gatling Test
My First Gatling Test

Эти 3 части образуют базовую структуру всех скриптов Gatling:

  • Конфигурация HTTP

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

  • Сценарий нагрузки

Давайте создадим еще пару скриптов, в которых рассмотрим функциональные возможности Gatling:

5.2 Пауза и проверка кодов ответа

В этом скрипте мы добавим паузы (pause) между запросами. Мы также будем проверять полученный код ответа HTTP.

Создайте новый класс Scala в папке simulations под названием CheckResponseCode. Добавьте следующий код:

package simulations

import io.gatling.core.Predef._
import io.gatling.http.Predef._

import scala.concurrent.duration.DurationInt

class CheckResponseCode extends Simulation {

  val httpConf = http.baseUrl("http://localhost:8080/app/")
    .header("Accept", "application/json")

  val scn = scenario("Video Game DB - 3 calls")

    .exec(http("Get all video games - 1st call")
      .get("videogames")
      .check(status.is(200)))
    .pause(5)

    .exec(http("Get specific game")
      .get("videogames/1")
      .check(status.in(200 to 210)))
    .pause(1, 20)

    .exec(http("Get all Video games - 2nd call")
      .get("videogames")
      .check(status.not(404), status.not(500)))
    .pause(3000.milliseconds)

  setUp(
    scn.inject(atOnceUsers(1))
  ).protocols(httpConf)

}

5.2.1 Паузы

В приведенном выше скрипте мы делаем 3 вызова API. Один к videogames, затем один к videogames/1, а затем еще один к videogames.

Мы добавили разные паузы в конце каждого вызова.

Сначала в строке 18 мы добавили .pause(5) - это пауза на 5 секунд.

Затем в строке 24 мы сделали .pause(1, 20) - это пауза на случайное время от 1 до 20 секунд.

Наконец, в строке 29 у нас .pause(3000.milliseconds) - как можно догадаться, это приостановило скрипт Gatling на 3000 миллисекунд. Примечание: Чтобы Gatling мог распознавать milliseconds, нам необходимо импортировать scala.concurrent.duration.DurationInt в наш класс.

5.2.2 Проверка кодов ответа

Для каждого из наших вызовов API Gatling проверяет код ответа, который приходит к нам обратно. Если код ответа не соответствует ассерту, Gatling выдаст ошибку.

В строке 17 мы написали .check(status.is(200))) - чтобы проверить, что код ответа - 200.

Затем в строке 23 у нас .check(status.in(200 to 210))), что проверяет, находится ли код ответа в диапазоне от 200 до 210.

Наконец, в строке 28 мы проверяем, что код ответа НЕ был чем-то из .check(status.not(404, status.not(500))) - чтобы проверить, что код ответа не 404 или 500.

5.3 Корреляция в Gatling с Check API

Check API в Gatling используется для двух вещей:

  • Ассерта, что ответ содержит некоторые ожидаемые данные.

  • Извлечение данных из этого ответа.

В этом примере кода мы будем использовать JSONPath для проверки некоторого текста в теле ответа. Затем мы будем использовать JSONPath в другом вызове, чтобы извлечь некоторые данные из тела ответа и сохранить эти данные в переменной. А затем мы будем использовать эту переменную в следующем вызове API.

Этот процесс называется корреляцией (correlation) и является распространенным сценарием в нагрузочного тестировании и тестировании производительности.

Создайте новый скрипт в папке simulations с именем CheckResponseBodyAndExtract. Вот его код:

package simulations

import io.gatling.core.Predef._
import io.gatling.http.Predef._

class CheckResponseBodyAndExtract extends Simulation {

  val httpConf = http.baseUrl("http://localhost:8080/app/")
    .header("Accept", "application/json")

  val scn = scenario("Check JSON Path")

      // First call - check the name of the game
      .exec(http("Get specific game")
      .get("videogames/1")
      .check(jsonPath("$.name").is("Resident Evil 4")))

      // Second call - extract the ID of a game and save it to a variable called gameId
      .exec(http("Get all video games")
      .get("videogames")
      .check(jsonPath("$[1].id").saveAs("gameId")))

      // Third call - use the gameId variable saved from the above call
      .exec(http("Get specific game")
      .get("videogames/${gameId}")
      .check(jsonPath("$.name").is("Gran Turismo 3"))

  setUp(
    scn.inject(atOnceUsers(1))
  ).protocols(httpConf)

}

Здесь мы выполняем 3 разных вызова API. В первом вызове мы используем JSONPath, чтобы проверить, что значение по ключу name в возвращенном JSON соответствует Resident Evil 4 :

  .exec(http("Get specific game")
  .get("videogames/1")
  .check(jsonPath("$.name").is("Resident Evil 4")))

Вышеупомянутый вызов был просто проверкой и ассертом названия игры. Во втором вызове мы фактически извлекаем значение и сохраняем его в переменной. Мы извлекаем id второй игры, возвращенной в JSON (т.е. игру с индексом 1):

  .exec(http("Get all video games")
  .get("videogames")
  .check(jsonPath("$[1].id").saveAs("gameId")))

Теперь, когда у нас есть переменная gameId, мы можем использовать ее в URL-адресе третьего вызова API:

  .exec(http("Get specific game")
  .get("videogames/${gameId}")
  .check(jsonPath("$.name").is("Gran Turismo 3"))

Вот так можно выполнить корреляцию в Gatling, извлекая данные с помощью метода .check(), а затем сохраняя в переменную с помощью .saveAs

Вы можете узнать больше о Check API в документации Gatling.

5.4. Повторное использование кода в Gatling с помощью методов и циклических вызовов

В примерах скриптов, которые мы видели до сих пор, мы записываем весь код для каждого HTTP-вызова индивидуально. Если мы хотим сделать еще один HTTP-вызов той же конечной точки, было бы лучше преобразовать этот код в метод.

Таким образом этапы нашего сценария выглядят более понятными, поскольку логика каждого этапа абстрагируется.

Создайте новый скрипт в папке simulations под именем CodeReuseWithObjects. Вот его код:

package simulations

import io.gatling.core.Predef._
import io.gatling.http.Predef._

class CodeReuseWithObjects extends Simulation {

  val httpConf = http.baseUrl("http://localhost:8080/app/")
    .header("Accept", "application/json")


  def getAllVideoGames() = {
    repeat(3) {
      exec(http("Get all video games - 1st call")
        .get("videogames")
        .check(status.is(200)))
    }
  }

  def getSpecificVideoGame() = {
    repeat(5) {
      exec(http("Get specific game")
        .get("videogames/1")
        .check(status.in(200 to 210)))
    }
  }

  val scn = scenario("Code reuse")
      .exec(getAllVideoGames())
      .pause(5)
      .exec(getSpecificVideoGame())
      .pause(5)
      .exec(getAllVideoGames())

  setUp(
    scn.inject(atOnceUsers(1))
  ).protocols(httpConf)

}

Мы определили два разных метода для двух разных вызовов API, которые мы производим в это скрипте. Первый — для getAllVideoGames():

  def getAllVideoGames() = {
    repeat(3) {
      exec(http("Get all video games - 1st call")
        .get("videogames")
        .check(status.is(200)))
    }
  }

Обратите внимание, что мы добавили метод repeat(3) - это означает, что код внутри блока будет повторяться 3 раза (то есть он сделает 3 HTTP-вызова).

Второй HTTP-вызов - получить конкретную игру:

def getSpecificVideoGame() = {
    repeat(5) {
      exec(http("Get specific game")
        .get("videogames/1")
        .check(status.in(200 to 210)))
    }
  }

Мы снова добавили метод repeat(5), чтобы повторить этот вызов 5 раз.

Теперь мы можем вызывать эти методы в нашем сценарии вместе с паузой между каждым методом, а сам сценарий выглядит приятнее:

  val scn = scenario("Code reuse")
      .exec(getAllVideoGames())
      .pause(5)
      .exec(getSpecificVideoGame())
      .pause(5)
      .exec(getAllVideoGames())

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


Узнать подробнее о курсе «Нагрузочное тестирование».

Посмотреть запись открытого демо-урока «LoadRunner: разработка и отладка скрипта нагрузочного тестирования».

Источник: https://habr.com/ru/company/otus/blog/554620/


Интересные статьи

Интересные статьи

В принципе, я согласен с комментариями, что данная тема излишняя, так как существуют автоматические инструменты форматирования кодаИ к тому же у каждого своё мнение о кра...
Рассказываем, как развивались способы создания презентаций до появления привычных нам сервисов. Мы привыкли делать презентации в специальных программах или онлайн-редакторах: у нас...
В этой статье я собираюсь рассмотреть вопрос повышения скорости отчетов. Под отчетом я понимаю любой запрос в базу данных, который использует агрегирующие функции. Также, я собираюсь затронуть во...
Как обновить ядро 1С-Битрикс без единой секунды простоя и с гарантией работоспособности платформы? Если вы не можете закрыть сайт на техобслуживание, и не хотите экстренно разворачивать сайт из бэкапа...
Мы публикуем видео с прошедшего мероприятия. Приятного просмотра.