Всем привет! Это команда тестирования производительности Тинькофф, и мы продолжаем цикл статей о 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.
Полезные ссылки
Подробнее о сессии в Gatling.
Подробнее о составлении HTTP-запросов в Gatling.
Подробнее о фидерах в Gatling.
Подробнее о настройке протокола HTTP в Gatling.
mSnus
А Locust не пробовали?