В этой статье я собираюсь объяснить, как использовать проект Gatling для написания стресс-тестов для ваших конечных точек JAX-RS Java EE и как интегрировать их с Gradle и Jenkins Pipeline , поэтому вместо простых стресс-тестов у вас есть непрерывное стресс- тестирование, при котором каждый коммит может запускать такие тесты автоматически, предоставляя автоматические подтверждения и более важную графическую обратную связь для каждого выполнения, чтобы вы могли отслеживать, как меняется производительность вашего приложения.
Первое, что нужно разработать, — это JAX-RS JavaEE-сервис:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
@Path ( "/planet" ) @Singleton @Lock (LockType.READ) public class PlanetResources { @Inject SwapiGateway swapiGateway; @Inject PlanetService planetService; @Inject @AverageFormatter DecimalFormat averageFormatter; @GET @Path ( "/orbital/average" ) @Produces (MediaType.TEXT_PLAIN) @Asynchronous public void calculateAverageOfOrbitalPeriod( @Suspended final AsyncResponse response) { // Timeout control response.setTimeoutHandler(asyncResponse -> asyncResponse.resume(Response.status (Response.Status.SERVICE_UNAVAILABLE) .entity( "TIME OUT !" ).build())); response.setTimeout( 30 , TimeUnit.SECONDS); try { // SwapiGateway is an interface to swapi.co (Star Wars API) JsonObject planets = swapiGateway.getAllPlanets(); final JsonArray results = planets.getJsonArray( "results" ); // Make some calculations with the result retrieved from swapi.co double average = planetService.calculateAverageOfOrbitalPeriod(results); final Response averageResponse = Response.ok( averageFormatter.format(average)) .build(); response.resume(averageResponse); } catch (Throwable e) { response.resume(e); } } } |
В этом нет ничего особенного, это асинхронная конечная точка JAX-RS, которая подключается к сайту swapi.co , получает всю информацию о планетах Звездных войн, вычисляет среднее значение орбитального периода и, наконец, возвращает его в виде текста. Для простоты я не собираюсь показывать вам все остальные классы, но они довольно просты, и в конце поста я предоставлю вам репозиторий github.
Приложение упаковано в файл war и развернуто на сервере приложений. В этом случае в Apache TomEE 7, развернутый внутри официального образа Apache TomEE Docker .
Следующим шагом является настройка скрипта сборки Gradle с зависимостями Gatling . Поскольку Gatling написан на Scala, вам нужно использовать плагин Scala .
01
02
03
04
05
06
07
08
09
10
|
apply plugin: 'java' apply plugin: 'scala' def gatlingVersion = "2.1.7" dependencies { compile "org.scala-lang:scala-library:2.11.7" testCompile "io.gatling:gatling-app:${gatlingVersion}" testCompile "io.gatling.highcharts:gatling-charts-highcharts:${gatlingVersion}" } |
После этого пришло время написать наш первый стресс-тест. Важно отметить, что написание стресс-тестов для Gatling — это написание класса Scala с использованием предоставленного DSL. Даже для людей, которые никогда не видели Scala, интуитивно понятно, как им пользоваться.
Поэтому создайте каталог с именем src / test / scala и создайте новый класс AverageOrbitalPeriodSimulation.scala со следующим содержимым:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package org.starwars import io.gatling.core.Predef._ import io.gatling.http.Predef._ import scala.concurrent.duration._ import scala.util.Properties // Extends from Simulation class AverageOrbitalPeriodSimulation extends Simulation { // Gets the base URL where our service is running from environment/system property val LOCATION_PROPERTY = "starwars_planets_url" ; val location = Properties.envOrElse(LOCATION_PROPERTY, // configures the base URL val conf = http.baseURL(location) // defines the scenario to run, which in this case is a GET to endpoint defined in JAX-RS service val scn = scenario( "calculate average orbital period" ) .exec(http( "get average orbital period" ) .get( "rest/planet/orbital/average" )) .pause( 1 ) // instead of simulating 10 users at once, it adds gradullay the 10 users during 3 seconds // asserts that there is no failing requests and that at max each request takes less than 3 seconds setUp(scn.inject(rampUsers( 10 ).over( 3 seconds))) .protocols(conf) .assertions(global.successfulRequests.percent.is( 100 ), global.responseTime.max.lessThan( 3000 )) } |
Каждое моделирование должно расширять объект моделирования. Это моделирование берет базовый URL-адрес службы из среды или системного свойства starwars_planets_url, создает сценарий, указывающий на конечную точку, определенную в JAX-RS , и, наконец, в течение 3 секунд будет постепенно добавлять пользователей, пока не будут запущены 10 пользователей одновременно. Тест пройдет только в том случае, если все запросы будут выполнены менее чем за 3 секунды.
Теперь нам нужно запустить этот тест. Вы заметите, что это не тест JUnit, поэтому вы не можете выполнить тест Run As JUnit . Что вам нужно сделать, это использовать исполняемый класс, предоставленный Gatling, который требует, чтобы вы передавали в качестве аргумента класс моделирования. Это действительно легко сделать с Gradle .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
task runLoadTest(type: JavaExec) { // before runnign the task we need to compile the tests dependsOn testClasses description = 'Stress Test Calculating Orbital Period' classpath = sourceSets.main.runtimeClasspath + sourceSets.test.runtimeClasspath // if starwars_planets_url is not provided we add the DOCKER_HOST one automatically def starwarsUrl; if (!System.env.containsKey( 'starwars_planets_url' ) && !System.properties.containsKey( 'starwars_planets_url' )) { if (System.env.containsKey( 'DOCKER_HOST' )) { starwarsUrl = System.env.DOCKER_HOST.replace( "tcp" , "http" ).replace( "2376" , "9090" ) + "/starwars/" } else { } } jvmArgs = [ "-Dgatling.core.directory.binaries=${sourceSets.test.output.classesDir.toString()}" ] // Means that the url has been calculated here and we set it if (starwarsUrl != null ) { environment[ "starwars_planets_url" ] = starwarsUrl } // Gatling application main = "io.gatling.app.Gatling" // Specify the simulation to run and output args = [ "--simulation" , "org.starwars.AverageOrbitalPeriodSimulation" , "--results-folder" , "${buildDir}/reports/gatling-results" , "--binaries-folder" , sourceSets.test.output.classesDir.toString(), "--output-name" , "averageorbitalperiodsimulation" , "--bodies-folder" , sourceSets.test.resources.srcDirs.toList().first().toString() + "/gatling/bodies" , ] } // when running test task we want to execute the Gatling test test.dependsOn runLoadTest |
Мы определяем задачу Gradle типа JavaExec , поскольку нам нужно запустить исполняемый класс. Затем мы немного облегчаем жизнь разработчику, автоматически определяя, что если starwars_planets_url не задан, мы запускаем этот тест на машине, на которой установлен Docker, поэтому, вероятно, именно этот хост будет использоваться.
Наконец, мы перезаписываем переменную среды, если она требуется, мы устанавливаем класс runnable с необходимыми свойствами и настраиваем Gradle для выполнения этой задачи каждый раз, когда выполняется тестовая задача (./gradlew test).
Если вы запустите его, вы можете увидеть некоторые выходные сообщения от Гатлинга , а в конце концов такое сообщение, как: откройте следующий файл: /Users/…./stress-test/build/reports/gatling results / averageorbitalperiodsimulation-1459413095563 / index. HTML, и здесь вы можете получить отчет. Обратите внимание, что случайное число добавляется в конец каталога, и это важно, как мы увидим позже. Отчет может выглядеть так:
В настоящее время мы интегрировали Гатлинга с Gradle , но здесь отсутствует часть, и она добавляет непрерывную часть в уравнение. Для добавления непрерывного стресс-тестирования мы будем использовать Jenkins и Jenkins Pipeline в качестве CI-сервера, поэтому для каждой фиксации выполняются стресс-тесты. среди других задач, таких как компиляция, запуск модуля, интеграционные тесты или контроль качества кода.
Исторически задания Jenkins настраивались с помощью веб-интерфейса, требуя, чтобы пользователи вручную создавали задания, заполняли детали задания и создавали конвейер через веб-браузер. Кроме того, это позволяет отделить конфигурацию задания от фактического создаваемого кода.
С введением плагина Jenkins Pipeline . Этот плагин представляет собой Groovy DSL, который позволяет реализовать весь процесс сборки в файле и сохранить его вместе с его кодом. Jenkins 2.0 поставляется по умолчанию с этим плагином, но если вы используете Jenkins 1.X, вы можете установить его как любой другой плагин ( https://wiki.jenkins-ci.org/display/JENKINS/Pipeline+Plugin )
Так что теперь мы можем начать кодировать наш плагин релиза, но для целей этого поста будет освещена только стрессовая часть. Вам нужно создать файл с именем Jenkinsfile (имя не обязательно, но это фактическое имя) в корне вашего проекта, и в этом случае со следующим содержимым:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
stage 'Compile And Unit Test' stage 'Code Quality' stage 'Integration Test' stage 'Acceptance Test' // defines an stage for info purposes stage 'Stress Test' def dockerHost = '...' //defines a node to run the stage node { // get source code from location where Jenkinsfile (this) is located. // you could use stash/unstash to get sources from previous stages instead of getting from SCM checkout scm // defines the environment variable for stress test // executes shell script sh './gradlew test' } } |
В этом случае мы определяем новый этап, который называется стресс-тест. Этап этап используется только в качестве информативного и будет использоваться для целей ведения журнала. Далее определяется узел. Узел является исполнителем Дженкинса, где выполнять код. Внутри этого узла исходный код извлекается из того же места, где находится Jenkinsfile, задает новую переменную среды, указывающую на место, где развернуто приложение, и, наконец, шаг оболочки, который выполняет тестовую задачу Gradle .
Последний шаг в Jenkins — это создание нового задания типа Pipeline и установка местоположения Jenkinsfile. Так что перейдите в Jenkins> New Item> Pipeline и дайте название работе.
Тогда вам нужно всего лишь перейти в раздел Pipeline и настроить хранилище SCM, в котором хранится проект.
И затем, если вы правильно настроили хуки от Jenkins и вашего сервера SCM, это задание будет выполняться для каждого коммита, поэтому ваши стресс-тесты будут выполняться непрерывно.
Конечно, вы, наверное, заметили, что стресс-тесты выполняются, но в Jenkins не публикуются отчеты, поэтому у вас нет возможности увидеть или сравнить результаты разных выполнений. По этой причине вы можете использовать плагин publishHtml для хранения сгенерированных отчетов в Jenkins . Если у вас еще не установлен плагин, вам нужно установить его как любой другой плагин Jenkins .
Плагин PublishHtml позволяет публиковать некоторые html-файлы, созданные с помощью нашего инструмента сборки, в Jenkins, чтобы они были доступны пользователям и также классифицировались по номеру сборки. Вам необходимо настроить местоположение каталога файлов для публикации, и здесь мы находим первую проблему. Помните ли вы, что Гатлинг генерирует каталог со случайным числом? Таким образом, мы должны исправить это в первую очередь. Вы можете следовать различным стратегиям, но самая простая из них — просто переименовать каталог в известное статическое имя после тестов.
Откройте файл сборки Gradle и добавьте следующий контент.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
task(renameGatlingDirectory) << { // find the directory def report = {file -> file.isDirectory() && file.getName().startsWith( 'averageorbitalperiodsimulation' )} def reportDirectory = new File( "${buildDir}/reports/gatling-results" ).listFiles().toList() .findAll(report) .sort() .last() // rename to a known directory // should always work because in CI it comes from a clean execution reportDirectory.renameTo( "${buildDir}/reports/gatling-results/averageorbitalperiodsimulation" ) } // it is run after test phase test.finalizedBy renameGatlingDirectory |
Мы создаем новую задачу, выполняемую в конце тестовой задачи, которая переименовывает последний созданный каталог в усредненный орбитальный период .
Последний шаг — добавить после вызова оболочки в следующий вызов Jenkinsfile:
1
|
publishHTML(target: [reportDir: 'stress-test/build/reports/gatling-results/averageorbitalperiodsimulation' , reportFiles: 'index.html' , reportName: 'Gatling report' , keepAll: true ]) |
После этого вы можете увидеть ссылку на странице работы, которая указывает на отчет.
И все, благодаря Gradle и Jenkins вы можете легко реализовать стратегию непрерывного стресс-тестирования и просто использовать код на языке, на котором говорят все разработчики.
Мы продолжаем учиться,
Алекс.
Ссылка: | Непрерывное стресс-тестирование для ваших приложений JAX-RS (и JavaEE) с Gatling + Gradle + Jenkins Pipeline от нашего партнера JCG Алекса Сото в блоге One Jar To Rule All . |