Всем привет! Это команда тестирования производительности Тинькофф, и мы продолжаем цикл статей о Gatling. 

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

Дисклеймер

На момент написания статьи используемые плагины поддерживают версию Gatling не выше 3.7.6. Для получения работающего скрипта в будущем достаточно обновить версии плагинов в соответствии с их документацией.

Что такое HTTP-протокол

HyperText Transfer Protocol — протокол прикладного уровня передачи данных. Изначально — в виде гипертекстовых документов в формате HTML. Сейчас протокол используют для передачи произвольных данных. 

Основа HTTP 1.1 — технология «клиент — сервер». В этой схеме есть:

  • потребители (клиенты), которые инициируют соединение и посылают запрос;

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

Gatling также имеет поддержку HTTP/2, но она пока экспериментальная. Как только добавят полноценную поддержку, мы обязательно напишем про это статью.

Как разработать скрипт для тестирования

В качестве тестового сервиса будем использовать сайт Computer Database. Пожалуйста, не подавайте на него нагрузку — используйте сайт только для отладки ваших скриптов.

Для скриптов реализуем такие запросы: вход на главную страницу и создание нового компьютера. Мы не будем разрабатывать проект с нуля, а используем уже готовый шаблон и с его помощью создадим проект ​​myhttpservice. Процесс создания мы описывали в предыдущей статье. А теперь разберемся, как создать нужный нам скрипт для тестирования HTTP-протокола.

Шаг 1. Обновляем зависимости

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

В файле project/Dependencies.scala проверьте актуальную версию Gatling и версию gatlingPicatinny.

 lazy val gatling: Seq[ModuleID]          = Seq(
    "io.gatling.highcharts" % "gatling-charts-highcharts",
    "io.gatling"            % "gatling-test-framework",
  ).map(_ % "[current_version]" % Test)

  lazy val gatlingPicatinny: Seq[ModuleID] = Seq(
    "ru.tinkoff" %% "gatling-picatinny",
  ).map(_ % "[current_version]")

В файле build.sbt проверьте, что версия scala поддерживается текущей версией Gatling.

scalaVersion := "[support_version]"

Загрузите новые зависимости в проект, запустив команду в консоли. Готово — вы обновили зависимости.

sbt update

Шаг 2. Меняем переменные

В файле src/test/resources/simulation.conf хранятся дефолтные переменные для запуска. Для нашего сервиса нужно обновить только baseUrl.

baseUrl: "http://computer-database.gatling.io"

Шаг 3. Создаем Action для публикации сообщений

В директории сases создайте новый файл для объекта HttpActions. В нем опишите все необходимые действия. Например, давайте создадим два запроса: get и post. Добавим к запросам параметры, тело запроса и проверки. Проверки помогут понять, успешно ли завершился запрос. Для большего разнообразия параметризуем запрос createNewComputer. Параметры "#{randomComputerName}", "#{introduced}", "#{discontinued}" позволят использовать значения, полученные из Feeder. Параметр "#{company}" мы получаем из ответа на предыдущий запрос pressButtonAddNewComputer.

package ru.tinkoff.load.myhttpservice.cases

import io.gatling.core.Predef.
import io.gatling.http.Predef.
import io.gatling.http.request.builder.HttpRequestBuilder

object Actions {

 val openMainPage: HttpRequestBuilder =
   // Указываем имя запроса
   http("Open main page")
     // Указываем тип запроса (метод) и эндпоинт
     .get("/")
     .check(
       // Проверяем, что в ответе пришел ОК
       // (по умолчанию Gatling проверяет ответы на все успешные коды возврата: 2xx, 3xx)
       status is 200,
     )

 val pressButtonAddNewComputer: HttpRequestBuilder =
   http("pressButtonAddNewComputer")
     .get("/computers/new")
     .check(
       status is 200,
     )
     .check(
       // Забираем из ответа случайную компанию и сохраняем значение 
       // в переменной 'company'
       regex("<option value=\"(.+?)\"").findRandom.saveAs("company"),
     )

 val createNewComputer: HttpRequestBuilder =
   http("createNewComputer")
     .post("/computers")
      // Указываем параметры запроса. Обратите внимание на значение, 
      // таким образом мы можем параметризовать запросы.
     .formParam("name", "#{randomComputerName}")
     .formParam("introduced", "#{introduced}")
     .formParam("discontinued", "#{discontinued}")
     // Тут вместо #{company} подставится значение из предыдущего запроса                                     
     .formParam("company", "#{company}")
}

Шаг 4. Используем Feeders

Узнать о Feeders побольше можно из предыдущей статьи. В нашем примере мы будем использовать фидеры из подключаемой библиотеки gatling-picatinny. Для этого в директории myhttpservice создайте новую директорию feeders, а в ней — object Feeders.

package ru.tinkoff.load.myhttpservice.feeders

import io.gatling.core.feeder._
import ru.tinkoff.gatling.feeders._

object Feeders {

  // Для имени компьютера будем использовать случайную строку с нужным алфавитом.
  val randomComputerName: Feeder[String] =
    RandomRangeStringFeeder("randomComputerName", alphabet = ('A' to 'Z').mkString)

  // Используем фидер для создания случайной даты
  val introducedDate: Feeder[String] = RandomDateFeeder("introduced")

  // Создаем случайную дату со сдвигом относительно 'introduced' даты
  val discontinuedDate: Feeder[String] = RandomDateRangeFeeder("introduced", "discontinued", 3)

  // Объединяем фидеры в одну переменную для удобства                                                  
  val feeders: Feeder[Any] = randomComputerName ** introducedDate ** discontinuedDate

}

Шаг 5. Пишем сценарий теста

Для этого измените код в CommonScenario. Опишите класс CommonScenario, в котором создаете сценарий — порядок выполнения определенных действий.

package ru.tinkoff.load.myhttpservice.scenarios

import io.gatling.core.Predef._
import io.gatling.core.structure.ScenarioBuilder
import ru.tinkoff.load.myhttpservice.cases.Actions._
import ru.tinkoff.load.myhttpservice.feeders.Feeders._

/*
Объект-компаньон для класса CommonScenario,
по сути синтаксический сахар, чтобы можно было вызвать сценарий
таким образом CommonScenario(), вместо new CommonScenario().scn
*/
object CommonScenario {
  def apply(): ScenarioBuilder = new CommonScenario().scn
}

class CommonScenario {

  // Создаем сценарий и его имя
  val scn: ScenarioBuilder = scenario("CommonScenario")
    // Подключаем наши фидеры
    .feed(feeders)
    // Подключаем наши запросы
    .exec(pressButtonAddNewComputer)
    .exec(createNewComputer)
}

Шаг 6. Создаем описание HTTP-протокола

По умолчанию в проекте из шаблона g8 уже настроен протокол HTTP. Для нашего скрипта в файле myhttpserviсe.scala опишем еще один протокол. 

package ru.tinkoff.load

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.http.protocol.HttpProtocolBuilder
// Подключаем стандартные переменные из gatling picatinny
import ru.tinkoff.gatling.config.SimulationConfig._

package object myhttpservice {

  val httpProtocol: HttpProtocolBuilder = http
    // Используем стандартную переменную из gatling picatinny, значение которой подтянется из simulation.conf
    .baseUrl(baseUrl)
    // Базовые заголовки. Для большинства кейсов этого будет достаточно и ничего менять не нужно.                     
    .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
    .acceptEncodingHeader("gzip, deflate")
    .acceptLanguageHeader("en-US,en;q=0.5")
    .userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")
    // Если требуется добавить авторизацию, можно использовать специальные методы.
    .authorizationHeader("Bearer token")
    // Или определить свой заголовок
    .header("customHeader", "value")
}

Шаг 7. Подготавливаем нагрузочные тесты

В файле Debug.scala раскомментируйте строку с proxy для локальной отладки. Перед заливкой в VCS нужно либо убрать proxy, либо закомментировать его. 

package ru.tinkoff.load.myhttpservice

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import ru.tinkoff.gatling.config.SimulationConfig._
import ru.tinkoff.load.myhttpservice.scenarios.CommonScenario

class Debug extends Simulation {

  setUp(
    // Запускаем наш сценарий
    CommonScenario()
      // Запускать будет один пользователь - одну итерацию
      .inject(atOnceUsers(1)),
  ).protocols(
    // Работа будет проходить по протоколу, который описан в конфигурации httpProtocol
    httpProtocol
      // Настраиваем прокси для отладки запросов, используем для этого fiddler 
      // или charles (8888 стандартный порт для прокси)
      .proxy(Proxy("localhost", 8888).httpsPort(8888))                                                
  )
    // Максимальное время теста равно testDuration, если тест не завершится за меньшее время, 
    // он будет остановлен автоматически
    .maxDuration(testDuration)
}

Для MaxPerformance- и Stability-тестов ничего менять не нужно. Подойдут дефолтные настройки для HTTP.

Шаг 8. Запускаем Debug-тест

Для запуска используйте GatlingRunner. После выполнения скрипта можно посмотреть консоль fiddler или charles и увидеть наши запросы.

package ru.tinkoff.load.myhttpservice

import io.gatling.app.Gatling
import io.gatling.core.config.GatlingPropertiesBuilder

object GatlingRunner {

  def main(args: Array[String]): Unit = {

    // Указывает имя симуляции Debug, либо какой-то другой, 
    // например, MaxPerformance
    val simulationClass = classOf[Debug].getName

    val props = new GatlingPropertiesBuilder
    props.simulationClass(simulationClass)

    Gatling.fromMap(props.build)
  }

}

Так, например, выглядят наши запросы в charles.

Также скрипт можно запустить из консоли командой.

sbt "Gatling / testOnly ru.tinkoff.load.myhttpservice.Debug"

Ниже — пример успешного запуска скрипта.

Simulation ru.tinkoff.load.myhttpservice.Debug started...                             

================================================================================
2022-01-27 11:44:22                                           0s elapsed
---- Requests ------------------------------------------------------------------
> Global                                                   (OK=3      KO=0     )
> pressButtonAddNewComputer                                (OK=1      KO=0     )
> createNewComputer                                        (OK=1      KO=0     )
> createNewComputer Redirect 1                             (OK=1      KO=0     )

---- CommonScenario ------------------------------------------------------------
[##########################################################################]100%
          waiting: 0      / active: 0      / done: 1     
================================================================================

Simulation ru.tinkoff.load.myhttpservice.Debug completed in 0 seconds
Parsing log file(s)...
Parsing log file(s) done
Generating reports...

================================================================================
---- Global Information --------------------------------------------------------
> request count                                          3 (OK=3      KO=0     )
> min response time                                    153 (OK=153    KO=-     )
> max response time                                    319 (OK=319    KO=-     )
> mean response time                                   209 (OK=209    KO=-     )
> std deviation                                         78 (OK=78     KO=-     )
> response time 50th percentile                        154 (OK=154    KO=-     )
> response time 75th percentile                        237 (OK=237    KO=-     )
> response time 95th percentile                        303 (OK=303    KO=-     )
> response time 99th percentile                        316 (OK=316    KO=-     )
> mean requests/sec                                      3 (OK=3      KO=-     )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                             3 (100%)
> 800 ms < t < 1200 ms                                   0 (  0%)
> t > 1200 ms                                            0 (  0%)
> failed                                                 0 (  0%)
================================================================================

Заключение

Мы рассказали, как быстро и легко создать проект из нашего шаблона и написать скрипты для HTTP-протокола. В этом нам помогла наша библиотека Picatinny.

В следующих статьях поговорим о том, как написать скрипты для протоколов gRPC,  AMQP, JDBC и kafka.

Полезные ссылки

  1. Подробнее о сессии в Gatling.

  2. Подробнее о составлении HTTP-запросов в Gatling.

  3. Подробнее о фидерах в Gatling.

  4. Подробнее о настройке протокола HTTP в Gatling.

  5. Проект Gatling из примеров этой статьи.

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


  1. mSnus
    01.04.2022 13:16

    А Locust не пробовали?