이 연재글은 Gatling을 이용한 웹 애플리케이션 부하 테스트의 2번째 글입니다.

이전에 실습한 Gatling 웹페이지 부하 테스트에 이어 이번장에서는 Scala로 작성한 테스트 코드로 Rest API 서버에 부하를 주는 테스트를 진행해보겠습니다. 일단 테스트할 Rest API가 필요하므로 이전에 작성한 프로젝트를 불러와서 서버를 띄우겠습니다.

사전준비

실습에서 사용할 RestAPI의 자세한 내용은 아래 포스팅에서 확인해 주십시오.
https://daddyprogrammer.org/post/series/springboot2-make-rest-api/

실습에서는 아래 Repository를 로컬 PC에 Clone 받아 서버를 띄우겠습니다.
https://github.com/codej99/SpringRestApi.git

서버 구동 시 Database가 필요하므로 아래 포스팅을 참고하여 H2 서버를 실행합니다.

위에서 다운로드한 Gradle 프로젝트를 빌드하여 Jar 파일을 생성하고 서버를 구동할 것입니다. 자세한 내용을 알고 싶으면 아래 포스팅을 참고해 주십시오.

Executable Jar 생성

Intellij에서 서버를 실행해도 되지만 서버 소스를 수정할 것이 아니므로 바로 Jar파일을 생성하여 서버를 띄우겠습니다. 프로젝트 Root에서 다음 명령을 입력하여 실행 가능한 Jar를 생성합니다. Jar파일은 build/libs에 생성됩니다.

// Linux or Mac
$ ./gradlew bootJar
// Windows
$ gradlew.bat bootJar
$ cd build/libs
$ ls
api-0.0.1-SNAPSHOT.jar 

Jar파일로 API 서버 실행

Java -jar 명령으로 Jar를 실행하면 서버가 구동됩니다. 로그상에 에러가 출력되지 않고 8080 포트로 서버가 실행되었다면 정상적으로 구동된 것입니다. (서버를 중단하려면 Ctrl+C를 입력하면 됩니다.) 한번 더 확인을 위해 브라우저에서 Swagger를 호출해 봅니다.

 % java -jar api-0.0.1-SNAPSHOT.jar 

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.4.RELEASE)

2020-06-26 16:39:20.569  INFO 85983 --- [           main] com.rest.api.SpringRestApiApplication    : Starting SpringRestApiApplication on macbook.local with PID 85983 (/Users/abel/IdeaProjects/SpringRestApi/build/libs/api-0.0.1-SNAPSHOT.jar started by abel in /Users/abel/IdeaProjects/SpringRestApi/build/libs)
2020-06-26 16:39:20.571 DEBUG 85983 --- [           main] com.rest.api.SpringRestApiApplication    : Running with Spring Boot v2.1.4.RELEASE, Spring v5.1.6.RELEASE
2020-06-26 16:39:20.571  INFO 85983 --- [           main] com.rest.api.SpringRestApiApplication    : The following profiles are active: local
// 생략....
2020-06-26 16:39:27.417  INFO 85983 --- [           main] d.s.w.p.DocumentationPluginsBootstrapper : Context refreshed
2020-06-26 16:39:27.440  INFO 85983 --- [           main] d.s.w.p.DocumentationPluginsBootstrapper : Found 1 custom documentation plugin(s)
2020-06-26 16:39:27.454  INFO 85983 --- [           main] s.d.s.w.s.ApiListingReferenceScanner     : Scanning for api listing references
2020-06-26 16:39:27.800  INFO 85983 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-06-26 16:39:27.802  INFO 85983 --- [           main] com.rest.api.SpringRestApiApplication    : Started SpringRestApiApplication in 7.899 seconds (JVM running for 8.316)

http://localhost:8080/swagger-ui.html 주소를 브라우저에서 실행하여 정상적으로 Swagger가 출력되는지 확인합니다.

테스트 데이터 생성

Swagger를 이용해 테스트에 사용할 회원 정보를 사전에 생성해 놓습니다.

  1. Sign – POST /v1/signup 을 실행하여 계정을 하나 생성합니다.
  2. 생성한 계정으로 로그인(POST /v1/signin)하고 결과 data(token)을 복사해 놓습니다.
  3. 복사한 token을 이용하여 게시판(freeboard)을 하나 생성합니다.(POST /v1/board/{boardName})

여기까지 완료하면 사전 준비는 끝입니다. 간단한 테스트 코드를 작성하고 Gatling을 실행해 보겠습니다.

Gatling 테스트 코드 작성

이전 실습에서 구성한 Intellij 환경에 테스트 코드를 작성합니다. src/test/scala/org/example 아래에 FirstSimulation.scala를 생성합니다.

테스트 시나리오는 다음과 같습니다.

유저 1명이

  • 게시글 리스트 확인
  • 게시판에 글 작성
  • 작성한 글 확인

시나리오를 작성하기 위한 http_protocol 작성 방법은 아래 공식 문서에서 자세한 내용을 참고하시면 됩니다.

https://gatling.io/docs/current/http/http_request

시나리오대로 작성한 코드는 아래와 같으며 대강 어떻게 테스트 코드를 작성해야 하는지 가늠할 수 있습니다.

package org.example

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

class FirstSimulation extends Simulation {

  val httpProtocol = http.baseUrl("http://localhost:8080")
    .acceptHeader("application/json")
    .acceptEncodingHeader("gzip, deflate")
    .acceptLanguageHeader("ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,id;q=0.6,th;q=0.5,zh-TW;q=0.4,zh;q=0.3")
    .userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36")

  val authHeaders = Map(
    "X-AUTH-TOKEN" -> "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwicm9sZXMiOlsiUk9MRV9VU0VSIl0sImlhdCI6MTU5MzE1Nzc0MywiZXhwIjoxNTkzMjQ0MTQzfQ.GH32pWlt8TB6yUdAtg8So0vDKituyNi0r1YJ9j7RXtM")

  val formParam = Map(
    "author" -> "아무게",
    "title" -> "첫인사드립니다.",
    "content" -> "안녕하세요.아무게입니다.잘부탁드립니다."
  )

  val postId = 0

  val scn = scenario("FirstSimulation")
    .exec(http("게시판 보기")
        .get("/v1/board/freeboard/posts"))
    .pause(2)
    .exec(http("게시판에 글 작성하기")
        .post("/v1/board/freeboard/post")
        .headers(authHeaders)
        .formParamMap(formParam)
        .check(status.is(200))
        .check(jsonPath("$.data['postId']").saveAs("postId")))
    .exec(http("작성한 글 확인하기")
        .get("/v1/board/post/${postId}"))

  setUp(scn.inject(atOnceUsers(1))).protocols(httpProtocol)
}

val httpProtocol

요청 시 공통으로 사용되는 정보들을 세팅합니다. BaseURL, Header등 공통으로 사용하는 정보를 담아 재사용 합니다.

val authHeaders

인증이 필요한 api 호출 시 세팅해야 할 token정보를 저장합니다.

val formParam

게시글 작성 시 입력받는 폼 값을 저장합니다.

scenario

위에서 작성한 테스트 시나리오를 실제 코드로 구현합니다. exec 명령을 이용하면 각각의 http api를 호출할 수 있습니다. 이때 http-method(get, post, put, delete 등)를 지정할 수 있으며 인증이 필요한 경우엔 header에 값을 세팅할 수 있습니다. 포스팅에 사용되는 Request parameter 정보는 formParamMap을 사용하여 주입할 수 있습니다.

check(status.is(200))

api 결과가 성공(Http Status Code == 200)인지 체크합니다.

check(jsonPath(“$.data[‘postId’]”).saveAs(“postId”)))

결과 json에 data.postId가 존재하는지 검사하고 값이 있으면 postId 변수에 값을 저장합니다. check에 대한 자세한 사용법은 아래 공식 문서를 확인해 주십시오.
https://gatling.io/docs/current/http/http_check

get(“/v1/board/post/${postId}”)

직전 요청의 결과 값(postId)으로 URL을 조합하여 게시글 상세 api를 호출합니다.

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

위에서 작성한 시나리오를 실행합니다. inject를 통해 가상 유저를 생성하여 부하를 주입할 수 있습니다.

이제 테스트 시나리오를 실행해 봅니다. Intellij에 구축했으므로 Engine을 실행하면 되는데 테스트 선택 화면에서 변경된 코드가 반영되지 않아 mvn:test를 한번 실행하고 다시 시도해야 합니다. (제가 maven을 잘 몰라서..ㅜㅜ) 여러 차례 수정 및 반복 실행이 필요한 테스트에서는 이러한 점이 사용성을 너무 떨어뜨려 Intellij 환경의 테스트는 여기서 포기하고 아래와 같이 VSCode로 작업환경을 다시 구축했습니다.

VSCode 개발환경 구축

본의 아니게 중간에 개발환경을 변경하게 되었습니다. 죄송합니다.(ㅜ.ㅜ) 이미 많은 부분 포스팅을 진행해버려서 내용을 수정하기가 어렵기도 했습니다. 이왕 적는 거 시행착오를 겪는 과정까지 모두 기록으로 남기는 것이 좋다고 생각했습니다.

일단 앞선 포스트에서 설명했던 Gatling zip 파일을 다운로드하여 알맞은 위치에 압축을 해제합니다.

https://repo1.maven.org/maven2/io/gatling/highcharts/gatling-charts-highcharts-bundle/3.3.1/gatling-charts-highcharts-bundle-3.3.1-bundle.zip

최근 핫한 IDE인 VSCode를 다운로드하여 설치합니다.

https://code.visualstudio.com/

설치 완료 후에는 VScode를 실행하여 몇가지 세팅을 진행합니다.

AutoSave 활성화

Code -> Preferences – Settings 들어가서 auto save 검색합니다. 기본 off로 설정되어있는데 afterDelay로 변경한 후 AutoSave Delay를 1000(1초)으로 설정합니다.

code 커맨드 활성화

code 커맨드는 터미널 어디에서든 code . 을 입력하면 해당 위치의 파일 및 디렉터리를 VSCode에서 바로 띄워주는 기능입니다. 생각보다 굉장히 편리한 기능이라 꼭 설치하고 사용하시기 바랍니다.

Pallete 창을 띄우고(cmd+shift+p 또는 메뉴-View-Command Pallete… 를 선택) 검색 창에 code를 입력하면 Shell Command: Install ‘code’ command in PATH가 검색되는데 클릭하여 설치합니다.

이제 터미널에서 VScode로 띄우고 싶은 코드를 명령어 한 번에 띄울 수 있습니다. Gatling을 압축 해제한 디렉터리로 이동하여 “code .”을 입력하면 아래와 같이 VSCode가 실행되고 입력한 위치의 디렉터리와 파일이 IDE에 노출됩니다. 이제 바로 코딩을 시작하면 됩니다!

~/Documents/gatling-charts-highcharts-bundle-3.3.1 $ code .

Gatling 테스트를 위한 Scala 코드는 user-files/simulations/computerdatabase 하위에 작성하면 자동으로 인식됩니다.

Scala Syntax 플러그인 설치

Scala로 코드 작성 시 문법 체크를 해주는 plugin을 설치합니다. Extension 화면을 호출하면(cmd+shift+x 혹은 View-Extensions를 선택.) IDE 왼쪽에 메뉴처럼 Extension이 펼쳐집니다. scala로 검색하여 맨 위에 나오는 Scala Syntax(official)를 설치합니다.

테스트 코드 작성 및 실행

이전에 Intellij에서 작성한 코드를 가져와 다시 실행해 보겠습니다. simulations/computerdatabase에 FirstSimulation.scala를 생성하거나 기존 코드를 복사, 붙여 넣기 합니다. 이때 package 위치가 변경되었으므로 첫 번째 줄을 package computerdatabase로 수정해주어야 합니다.

메뉴 – Terminal – New Terminal을 선택하여 IDE하단에 터미널을 띄웁니다. 프로젝트의 Root를 베이스 디렉터리로 띄워주는데 하위 디렉터리인 bin으로 이동합니다. 그리고 gatling.bat(window) 또는 gatling.sh(linux or Mac)를 입력하여 gatling을 실행합니다. 작성한 테스트 코드가 여러개이면 선택하여 실행할 수 있습니다. 수행 결과는 아래처럼 console 로그로 출력되며 보고서는 ~/Documents/gatling-charts-highcharts-bundle-3.3.1/results/ 하위에 html 파일로 생성됩니다.

~/Documents/gatling-charts-highcharts-bundle-3.3.1/bin $ ./gatling.sh
GATLING_HOME is set to ~/Documents/gatling-charts-highcharts-bundle-3.3.1
Choose a simulation number:
     [0] computerdatabase.FirstSimulation
     [1] computerdatabase.advanced.AdvancedSimulationStep01
     [2] computerdatabase.advanced.AdvancedSimulationStep02
     [3] computerdatabase.advanced.AdvancedSimulationStep03
     [4] computerdatabase.advanced.AdvancedSimulationStep04
     [5] computerdatabase.advanced.AdvancedSimulationStep05
0
Select run description (optional)
엔터
Simulation computerdatabase.FirstSimulation started...

================================================================================
2020-06-26 23:00:26                                           2s elapsed
---- Requests ------------------------------------------------------------------
> Global                                                   (OK=3      KO=0     )
> 게시판 보기                                                   (OK=1      KO=0     )
> 게시판에 글 작성하기                                              (OK=1      KO=0     )
> 작성한 글 확인하기                                               (OK=1      KO=0     )

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

Simulation computerdatabase.FirstSimulation completed in 2 seconds
Parsing log file(s)...
Parsing log file(s) done
Generating reports...

================================================================================
---- Global Information --------------------------------------------------------
> request count                                          3 (OK=3      KO=0     )
> min response time                                     13 (OK=13     KO=-     )
> max response time                                    136 (OK=136    KO=-     )
> mean response time                                    68 (OK=68     KO=-     )
> std deviation                                         51 (OK=51     KO=-     )
> response time 50th percentile                         56 (OK=56     KO=-     )
> response time 75th percentile                         96 (OK=96     KO=-     )
> response time 95th percentile                        128 (OK=128    KO=-     )
> response time 99th percentile                        134 (OK=134    KO=-     )
> mean requests/sec                                      1 (OK=1      KO=-     )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                             3 (100%)
> 800 ms < t < 1200 ms                                   0 (  0%)
> t > 1200 ms                                            0 (  0%)
> failed                                                 0 (  0%)
================================================================================

Reports generated in 0s.
Please open the following file: ~/Documents/gatling-charts-highcharts-bundle-3.3.1/results/firstsimulation-20200626140022392/index.html

부하는 다음과 같이 사전에 정의된 몇가지 형식을 선택하여 주입할 수 있습니다.

setUp(
  scn.inject(
    nothingFor(4 seconds), // 1
    atOnceUsers(10), // 2
    rampUsers(10) during (5 seconds), // 3
    constantUsersPerSec(20) during (15 seconds), // 4
    constantUsersPerSec(20) during (15 seconds) randomized, // 5
    rampUsersPerSec(10) to 20 during (10 minutes), // 6
    rampUsersPerSec(10) to 20 during (10 minutes) randomized, // 7
    heavisideUsers(1000) during (20 seconds) // 8
  ).protocols(httpProtocol)
)

  1. nothingFor(duration): 일정 시간 동안 대기합니다.
  2. atOnceUsers(nbUsers): 지정된 수의 사용자를 한 번에 주입합니다.
  3. rampUsers(nbUsers) during(duration): 주어진 지속 시간에 걸쳐 주어진 수의 사용자들을 선형적으로 늘려가며 주입합니다.
  4. constantUsersPerSec(rate) during(duration): 주어진 지속시간 동안 사용자를 일정한 비율로 증가 시키며 주입합니다. 사용자는 일정한 간격으로 주입됩니다.
  5. constantUsersPerSec(rate) during(duration) randomized: 주어진 지속시간 동안 사용자를 일정한 비율로 증가 시키며 주입합니다. 사용자는 랜덤한 간격으로 주입됩니다.
  6. rampUsersPerSec(rate1) to (rate2) during(duration): 주어진 지속시간 동안 사용자를 정의된 시작 비율에서 목표 비율까지 증가 시키며 주입합니다. 사용자는 일정한 간격으로 주입됩니다.
  7. rampUsersPerSec(rate1) to(rate2) during(duration) randomized: 주어진 지속시간 동안 사용자를 정의된 시작 비율에서 목표 비율까지 증가 시키며 주입합니다. 사용자는 랜덤한 간격으로 주입됩니다.
  8. heavisideUsers(nbUsers) during(duration): 단위 계단 함수로 주어진 지속시간 동안 사용자를 주입합니다. 자세한 내용은 수학적인 내용이라 다음 링크를 통해 확인하시면 됩니다.(https://en.wikipedia.org/wiki/Heaviside_step_function)

서버에 부하를 주입하는 방법은 아래 공식 문서에서 더 자세히 확인할 수 있습니다.

https://gatling.io/docs/current/general/simulation_setup/?highlight=inject

테스트 정보 주입

서비스 환경에서 테스트 시나리오를 작성하다 보면 외부 데이터를 주입해야 하는 경우가 발생합니다. Gatling에서는 feeder라는 이름으로 그 기능을 지원하고 있으며 feeder 파일을 user-files/resources 하위에 작성하고 scala에서 ${변수명}으로 불러서 사용할 수 있습니다.

https://gatling.io/docs/current/session/feeder

테스트

위에서 설명한 기능을 테스트해보기 위해 이전에 작성한 테스트 시나리오를 조금 수정합니다. 아래의 시나리오를 5초에 걸쳐 10명의 유저가 선형적으로 증가되도록 하여 테스트를 진행합니다.

  • 게시글 리스트 확인
  • 게시판에 글 작성
  • 작성한 글 확인

일단 10명의 회원정보를 주입해야 하므로 swagger에서 signup / signin을 9번 반복하여 총 10개의 회원 정보를 확보합니다. 확보한 정보로 user-files/resources 아래에 feeder csv를 작성합니다.

맨 위줄에는 데이터를 맵핑할 변수명을 컴마로 구분하여 작성하고 두 번째 줄부터 주입할 데이터를 입력합니다.

input-datas.csv

author,title,content,x-auth-token
김성모,모집공고,만화가 지망생 구합니다,eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwicm9sZXMiOlsiUk9MRV9VU0VSIl0sImlhdCI6MTU5MzIyODc1MiwiZXhwIjoxNTkzMzE1MTUyfQ.vRsxr9H-XDUGe3-yllTL5k6JoU7RqqgzhuekwQUfkkM
나경은,직거래합니다,어린이 장난감 싸게 팔아요.,eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI2Iiwicm9sZXMiOlsiUk9MRV9VU0VSIl0sImlhdCI6MTU5MzIyODgyMCwiZXhwIjoxNTkzMzE1MjIwfQ.p-H-Qb0Q7J_jNgIrn4PtSETwHWTx0Z_mr0FInFOAAnA
다람쥐,다람쥐 분양합니다,다람쥐 새끼 3마리 분양합니다.,eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI3Iiwicm9sZXMiOlsiUk9MRV9VU0VSIl0sImlhdCI6MTU5MzIyODg1OSwiZXhwIjoxNTkzMzE1MjU5fQ.W00nDdZKhpHbVyQ31VpvO5GSvVzOTh9p26qIe-rMIQI
라이언,영어 가르쳐드립니다,영국사람입니다. 잘 가르쳐드립니다.,eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI4Iiwicm9sZXMiOlsiUk9MRV9VU0VSIl0sImlhdCI6MTU5MzIyODg5NCwiZXhwIjoxNTkzMzE1Mjk0fQ.MHe1LR_4CrpdEIdqWoTMBn3gf0Uakg8wT-7_1ULxI90
마동석,터미네이터 만들어드려요,몸짱 만들어드리겠습니다.,eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMCIsInJvbGVzIjpbIlJPTEVfVVNFUiJdLCJpYXQiOjE1OTMyMjg5MzQsImV4cCI6MTU5MzMxNTMzNH0.P8GwOgxsF6qXJPBo4AEmftjjm34KgdBKjVYWn3uPmkM
바이커,오토바이 연수시켜드립니다,오토바이 잘타게 도와드립니다.,eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMSIsInJvbGVzIjpbIlJPTEVfVVNFUiJdLCJpYXQiOjE1OTMyMjg5NjgsImV4cCI6MTU5MzMxNTM2OH0.Z-rIQQWDAXuxxkN_h3fKmrLs93mYyVQV_oOYrrnB82s
사샤,러시아어 가르쳐드립니다,모스크바 좋아요.,eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMiIsInJvbGVzIjpbIlJPTEVfVVNFUiJdLCJpYXQiOjE1OTMyMjkwMDEsImV4cCI6MTU5MzMxNTQwMX0.zWgQ1lHA1NNW29QriAlsLzmqfMWfr8uOxgCcT2UF7dU
아이린,노래 가르쳐드립니다,케이팝 짱입니다.,eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMyIsInJvbGVzIjpbIlJPTEVfVVNFUiJdLCJpYXQiOjE1OTMyMjkwMzQsImV4cCI6MTU5MzMxNTQzNH0.LOmEKDRWiKWEcEWaWPtX3FrAFN-n5XpkS1Ppkt8Vfg8
자로,검법 가르쳐드립니다,화산파 고수입니다.,eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxNCIsInJvbGVzIjpbIlJPTEVfVVNFUiJdLCJpYXQiOjE1OTMyMjkwNjUsImV4cCI6MTU5MzMxNTQ2NX0.tS6bGaL8I8T1pKaxGJelHjUaydeaPNPpMSTqPUw5T8Q
차두리,축구 가르쳐드립니다,국가대표 축구선수 만들어드립니다.,eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxNSIsInJvbGVzIjpbIlJPTEVfVVNFUiJdLCJpYXQiOjE1OTMyNTk0MDcsImV4cCI6MTU5MzM0NTgwN30.Qdu-BWCDiXhW1fnWJ2StwMCHt6Wun1gD_r1DaE2VD6M

SecondSimulation.scala를 새로 생성하고 시나리오에 맞게 내용을 작성합니다.

package computerdatabase

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._

class SecondSimulation extends Simulation {
  val httpProtocol = http.baseUrl("http://localhost:8080")
    .acceptHeader("application/json")
    .acceptEncodingHeader("gzip, deflate")
    .acceptLanguageHeader("ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,id;q=0.6,th;q=0.5,zh-TW;q=0.4,zh;q=0.3")
    .userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36")

  val feeder = csv("input-datas.csv").queue
  val postId = 0

  val scn = scenario("SecondSimulation")
    .feed(feeder)
    .exec(http("게시판 보기")
        .get("/v1/board/freeboard/posts"))
    .exec(http("게시판에 글 작성하기")
        .post("/v1/board/freeboard/post")
        .headers(Map("X-AUTH-TOKEN" -> "${x-auth-token}"))
        .formParamMap(Map("author" -> "${author}","title" -> "${title}", "content" -> "${content}"))
        .check(status.is(200))
        .check(jsonPath("$.data['postId']").saveAs("postId")))
    .exec(http("작성한 글 확인하기")
        .get("/v1/board/post/${postId}"))

  setUp(
    scn.inject(
      rampUsers(10) during (5 seconds)
    ).protocols(httpProtocol)
  )
}

FirstSimulation과 달리 feeder를 변수로 선언하고 input-datas.csv를 읽어 senario에 데이터를 주입하는 것을 확인할 수 있습니다. feeder로 주입된 데이터는 ${변수명}으로 접근 가능합니다. 마지막으로 부하 설정은 rampUsers(10) during (5 seconds)으로 수정되었습니다.

테스트를 실행해보면 다음과 같은 결과를 얻을 수 있습니다. 시나리오안에 3개의 Request가 있어 총 call은 10*3 = 30이 발생하였고 해당 응답 시간 통계를 확인할 수 있습니다.

만약 부하 테스트중 실패가 발생하거나 응답시간이 너무 오래걸리는 요청이 발생하면 해당 api는 개선이 필요합니다.

./gatling.sh
GATLING_HOME is set to ~/Documents/gatling-charts-highcharts-bundle-3.3.1
Choose a simulation number:
     [0] computerdatabase.FirstSimulation
     [1] computerdatabase.SecondSimulation
     [2] computerdatabase.advanced.AdvancedSimulationStep01
     [3] computerdatabase.advanced.AdvancedSimulationStep02
     [4] computerdatabase.advanced.AdvancedSimulationStep03
     [5] computerdatabase.advanced.AdvancedSimulationStep04
     [6] computerdatabase.advanced.AdvancedSimulationStep05
1
Select run description (optional)
[엔터]
Simulation computerdatabase.SecondSimulation started...

================================================================================
2020-06-27 21:14:51                                           4s elapsed
---- Requests ------------------------------------------------------------------
> Global                                                   (OK=30     KO=0     )
> 게시판 보기                                                   (OK=10     KO=0     )
> 게시판에 글 작성하기                                              (OK=10     KO=0     )
> 작성한 글 확인하기                                               (OK=10     KO=0     )

---- SecondSimulation ----------------------------------------------------------
[##########################################################################]100%
          waiting: 0      / active: 0      / done: 10    
================================================================================

Simulation computerdatabase.SecondSimulation completed in 4 seconds
Parsing log file(s)...
Parsing log file(s) done
Generating reports...

================================================================================
---- Global Information --------------------------------------------------------
> request count                                         30 (OK=30     KO=0     )
> min response time                                      3 (OK=3      KO=-     )
> max response time                                     17 (OK=17     KO=-     )
> mean response time                                     9 (OK=9      KO=-     )
> std deviation                                          4 (OK=4      KO=-     )
> response time 50th percentile                          8 (OK=8      KO=-     )
> response time 75th percentile                         13 (OK=13     KO=-     )
> response time 95th percentile                         16 (OK=16     KO=-     )
> response time 99th percentile                         17 (OK=17     KO=-     )
> mean requests/sec                                      6 (OK=6      KO=-     )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                            30 (100%)
> 800 ms < t < 1200 ms                                   0 (  0%)
> t > 1200 ms                                            0 (  0%)
> failed                                                 0 (  0%)
================================================================================

Reports generated in 0s.
Please open the following file: ~/Documents/gatling-charts-highcharts-bundle-3.3.1/results/secondsimulation-20200627121445058/index.html

결과 리포트 html을 확인하면 다음과 같은 화면을 볼 수 있습니다. 그래프를 보면 요구한 내용대로 5초 동안 테스트가 진행되었고 10개의 데이터가 선형적으로 증가하며 주입되는 것을 확인할 수 있습니다.

여기까지 Gatling을 이용하여 RestAPI 부하 테스트를 진행해 보았습니다. 평소에 테스트 시나리오만 잘 작성해 놓는다면 언제든지 빠르게 테스트해 볼 수 있으므로 코드 품질 유지에 도움이 됩니다. 또한 API 성능이 어느 정도 인지 쉽게 파악 가능하여 API 개선을 해야 할지 서버 성능을 높여야 할지 지표로서도 활용할 수 있습니다.

Scouter APM + Gatling 조합을 통한 시너지 효과내기

Gatling은 정해진 시나리오대로 부하를 발생시키는 프로그램입니다. Gatling 자체에서 생성된 결과 데이터만으로도 유용하게 사용 가능하지만 Scouter APM(Application Performance Monitoring)과 연동하면 더 큰 시너지 효과를 낼 수 있습니다. Scouter와 관련된 내용은 아래 포스팅에서 자세한 내용을 확인할 수 있습니다.

이상으로 실습을 마치겠습니다. 실습에서 사용한 테스트 코드는 아래 Github에서 확인할 수 있습니다.

https://github.com/codej99/gatling-restapi.git

연재글 이동[이전글] Gatling을 이용한 웹 애플리케이션 부하 테스트(1) – 환경 구축 및 Recorder를 이용한 테스트코드 생성