Gatling の exec メソッド内の記述方法と、Sessionの扱い方のまとめ

# 概要

Gatlingのテストコードを記述するためにはexec内の書き方とSessionの扱い方を覚えなければいけません。
どれも　http://gatling.io/docs/2.1.7/index.html に記述されていることですが、
日本語で記載された記事が少ないので、全てではありませんが簡単にまとめました。
チュートリアルを実行できたレベルの人が見ることを想定してまとめています。

※この記事は、「Gatlingで何が出来るか」ではなく、「Gatlingのメソッドの使い方やコードの書き方」の視点より書いています。


# 複数のexec処理を実行する
## execをメソッドチェーンで記述する
execが複数ある時の一番シンプルな書き方です。Gatlingのサンプルにも記述されてます。

```scala
val search_exec = 
  exec(http("Home"))
  .exec(http("Search").get("/computers?f=macbook"))
  .exec(http("Select").get("/computers/6"))
```

## execの引数にexecを入れて記述する

コードを共通化したい時、この記述方法が必要となります。
メソッドチェーンは便利ですが、クラスでexecの処理を分けたい時は役に立ちません。
上のサンプルは、以下のように書くことも出来ます。execを複数のテストコードで使い回すことが可能になります。

```scala
object Search {
  val home = exec(http("Home"))
  val search = exec(http("Search").get("/computers?f=macbook"))
  val select = exec(http("Select").get("/computers/6"))
}

val search_exec = exec(Search.home, Search.search, Search.select)
```

同じexec処理を10回実行したかったら、こう書けばすみます。

```scala
val pattern = exec(Search.home, Search.search, Search.select)
val search_exec = repeat(10){exec(pattern)}
```

#exec の内部で使う変数を定義したい

Session.set、もしくは Session.setAll を使います。変数は ${foo}のように取り出せます。
Scalaなので、setAllの時は_を使ってSession変数を省略すると綺麗に書けます。

```scala
val session_set = exec(session => {
  val session1 = session.set("foo", "FOO")
  val session2 = session1.set("bar", "BAR")
  session2
})

val session_setall = exec({_.setAll(("foo", "FOO"), ("bar", "BAR"))})

val session_exec = exec(session_setall).exec(http("Select").get("/computers/{$foo}"))
```

注意点としては、setを行ごとに書くときは、毎回必ず変数化しましょう。
うっかり下のように書くと、当たり前ですが初めの"foo"はsetされません。（自分はこれ地味にハマりましたorz）

```scala
val session_set = exec(session => {
  session.set("foo", "FOO")
  session.set("bar", "BAR")
})
```

Sesionの中身を取り出したいときは、Session.getを使います。

```scala
val session_setall = exec({
  session.setAll(("foo", "FOO"), ("bar", "BAR"))
}).exec(session => {
  val foo = session.get("foo")
  println(foo)
})
```

# http.getをするとSessionに格納されている変数
以下の変数が使用できます。

 - bodyString
  - レスポンスボディ文字列
 - bodyBytes
  - レスポンスボディバイナリ
 - responseTimeInMillis
  - レスポンスタイム
 - latencyInMillis
  - レイテンシー

# 指定したBodyとHeaderとパラメータを渡したい

事前にBodyを生成してSessionに渡す必要があります。
XMLやJsonは、asXML, asJsonが使えます。

Headerは header()、パラメータはqueryParam()が使えます。

```scala
exec(session => {
  val body = "..."
  session.setAll(("uri", "http://..."), ("body", body))
}).exec({
  http(this.getClass.getName)
    .post("{$uri}"}
    .queryParam("hoge", 10)
    .body(StringBody("{$body}")).asJson
})
```

# 指定した秒数間隔で実行するパターンを検証したい

pace() を使います。1分間隔でAPIにアクセスさせたい！　というようなパターンに役立ちます。
ちなみに pause() もありますが、こちらは単純なsleepなので全くの別物です。

```scala
pace(60 seconds).exec(...)
```

# レスポンスのValidationをしたい

レスポンスのValidationにはcheck()を使います。
transformを使うと自由なvalidationを書くことが出来ます。
また、正規表現で抽出確認も出来ます。

```scala
exec(http("Home")
  .check(status.is(200 to 210))
  .check(responseTimeInMillis.transform(_ / 1000.0).greaterThan(10))
  .check(regex( """<result="ok"/>"""))
```

# レスポンスを保存したい

レスポンスの保存にはcheck()を使います。
（え、保存なのにcheckだって？　凄い違和感ありますよね。私もそう思います。）
なお、ここでもtransformを使ってデータの加工が出来ます。

```scala
exec(http("Home")
  .check(status.is(200 to 210).saveAs("status"))
  .check(bodyString.transform(_.size).saveAs("size")))
```

# Sessionに含まれてるかを確認したい。
Session.contains　が便利です。

```scala
  if(session.contains("hoge")) {
    ...
  }
```

# Sessionの中身で条件分岐したい

doif、doIfOrElse を使います。

```scala
.doIf("${myKey}", "myValue") {
   exec( http("...") ... )
}
```


# exec内部でFailureが発生したら、その地点でKO扱いにして終了させる
AのAPIへアクセスし、そのレスポンスの情報を変数化してBのAPIにアクセスする場合、
AのAPIへのアクセスが失敗しレスポンスが取得できないと、未定義の変数が使われるためExceptionが発生します。

対策としては exitHereIfFailed を使い、その地点でFailureが確定してる場合exitするようにします。
exec処理の最後に毎回書くのをオススメします。

```scala
object Search {
  val home = exec(http("Home").exitHereIfFailed
  val search = exec(http("Search").get("/computers?f=macbook")).exitHereIfFailed 
  val select = exec(http("Select").get("/computers/6")).exitHereIfFailed 
}
```

# Sessionの中身によって、OK/KOを決定したい。

残念ながら、exec(session => ...) の中ではOK/KOは決定できません。
checkメソッドでtransformを活用しまくり、validationで頑張りましょう。


# execで乱数を使いたい
Sessionでsetすれば使えます。
以下、事前に乱数の最大値maxがSessionに渡っている例です。

```scala
val random = new Random()
val e = exec((session: Session) => session.set("randNum", random.nextInt(session("max").as[Int])))
```

# Sessionの中身をprintしたい

as[] を使います。場合に応じて、StringやLongやIntなどにしましょう。

```scala
println("responseTime:", session("responseTimeInMillis").as[Long] / 1000.0)
```

# 実行シナリオ（アクセスパターン）

最後にシナリオのアクセスパターンを記述する必要があります。
アクセスするユーザー数が設定できるため、負荷の調整が出来ます。
シナリオは主に以下をよく使います。

 - atOnceUsers(10)
  - 1回のみ10ユーザーがアクセスします
 - rampUsers(10) over(5 seconds)
  - 5秒間の間に0から10ユーザーまで線形的に上昇しながらアクセスします
 - constantUsersPerSec(20) during(15 seconds)
  - 15秒間の間、一定で20ユーザー/秒でアクセスします
 - rampUsersPerSec(10) to(20) during(10 minutes) 
  - 10分間の間に、10ユーザー/秒から20ユーザー/秒まで線形的に上昇しながらアクセスします


# 他

GatlingはわからなくなったらGitHubのコード読めばいいと思います。
特にテストコードが超役に立ちます。

https://github.com/gatling/gatling/blob/8c9ff719abb7dec50bddccc7456e4b4e7571fe72/gatling-http/src/test/scala/io/gatling/http/HttpCompileTest.scala

こんな風にテストコードにたくさん実記述例が書かれてるので、サンプルが欲しくなったらテストあさるのが一番のオススメです。