KarateによるAPIのシナリオテスト自動化 #02 Reading By Example

# はじめに
[前回](https://qiita.com/takanorig/items/11fd65a4359280cb82e5)は、Karate Maven archetype が出力する example を実行してみました。
今回は、そのファイルの内容を見ていき、Karateに対する理解を深めていきたいと思います。

# KarateのExampleのファイル内容

Karate Maven archetype で出力される内容は、以下のような構成です。
以降で、ポイントとなるファイルの内容を確認していきます。

```
example-karate/
├── pom.xml
└── src
    ├── main
    │   └── java
    └── test
        └── java
            ├── examples
            │   ├── ExamplesTest.java
            │   └── users
            │       ├── UsersRunner.java
            │       └── users.feature
            ├── karate-config.js
            └── logback-test.xml
```
## pom.xml

前提として、今回利用しているのは Karate 0.7.0 になります。

```xml
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
    <maven.compiler.version>3.6.0</maven.compiler.version>
    <karate.version>0.7.0</karate.version>
  </properties>
```

dependencies では、以下の2つのライブラリが指定されています。

- karate-apache
- karate-junit4

2018/06/02現在、JUnit5には、まだ公式には対応されていないようです。

```xml
  <dependencies>
    <dependency>
      <groupId>com.intuit.karate</groupId>
      <artifactId>karate-apache</artifactId>
      <version>${karate.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.intuit.karate</groupId>
      <artifactId>karate-junit4</artifactId>
      <version>${karate.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
```

こちらは前回の注意点の再掲ですが、buildタグでは、以下のように```src/test/java``` をテストリソースに指定しておく必要があります（デフォルトは、```scr/test/resources```）。

```xml
  <build>
    <testResources>
      <testResource>
        <directory>src/test/java</directory>
        <excludes>
          <exclude>**/*.java</exclude>
        </excludes>
      </testResource>
    </testResources>
  </build>
```

## karate-config.js

Karate の共通設定のファイルです。
デフォルトは、以下の内容。

```js
function() {    
  var env = karate.env; // get system property 'karate.env'
  karate.log('karate.env system property was:', env);
  if (!env) {
    env = 'dev';
  }
  var config = {
    env: env,
	myVarName: 'someValue'
  }
  if (env == 'dev') {
    // customize
    // e.g. config.foo = 'bar';
  } else if (env == 'e2e') {
    // customize
  }
  return config;
}
```

基本的な設定内容は、以下を参照。

- https://github.com/intuit/karate#karate-configjs
- https://github.com/intuit/karate#configure

環境ごとに設定を切り替えたり、タイムアウト値などを設定できたりします。
httpsのREST-APIにアクセスすらう際にも、ここでSSLの設定をするらしい。

```
karate.configure('ssl', true);
```

## examples/ExamplesTest.java

こちらは、テストケースを実行するためのクラス。
テストスイート（テストケースをグルーピングするもの）として振る舞います。
そのため、ディレクトリの構成は、後述する ```XxxRunner``` との関係性を意識する必要があります。

```java
package examples;

import com.intuit.karate.junit4.Karate;
import org.junit.runner.RunWith;

@RunWith(Karate.class)
public class ExamplesTest {
    // this will run all *.feature files that exist in sub-directories
    // refer to https://github.com/intuit/karate#naming-conventions
    // for folder-structure recommendations and naming conventions
}
```

## examples/users/UsersRunner.java, users.feature

こちらが、テストケースの本体の内容です。
Runnerのファイルと、featureのファイルがセットになりますが、1:1である必要はありません。
Runnerのテスト実行クラスがあるディレクトリのfeatureファイルが読み込まれ、テストシナリオとして実行されます。

```java:UsersRunner.java
package examples.users;

import com.intuit.karate.junit4.Karate;
import org.junit.runner.RunWith;

@RunWith(Karate.class)
public class UsersRunner {
}
```

```cucumber:users.feature
Feature: sample karate test script    
    If you are using Eclipse, install the free Cucumber-Eclipse plugin from
    https://cucumber.io/cucumber-eclipse/
    Then you will see syntax-coloring for this file. But best of all,
    you will be able to right-click within this file and [Run As -> Cucumber Feature].
    If you see warnings like "does not have a matching glue code",
    go to the Eclipse preferences, find the 'Cucumber User Settings'
    and enter the following Root Package Name: com.intuit.karate    
    Refer to the Cucumber-Eclipse wiki for more: http://bit.ly/2mDaXeV

Background:
* url 'https://jsonplaceholder.typicode.com'

Scenario: get all users and then get the first user by id

Given path 'users'
When method get
Then status 200

* def first = response[0]

Given path 'users', first.id
When method get
Then status 200

Scenario: create a user and then get it by id

* def user =
"""
{
  "name": "Test User",
  "username": "testuser",
  "email": "test@user.com",
  "address": {
    "street": "Has No Name",
    "suite": "Apt. 123",
    "city": "Electri",
    "zipcode": "54321-6789"
  }
}
"""

Given url 'https://jsonplaceholder.typicode.com/users'
And request user
When method post
Then status 201

* def id = response.id
* print 'created id is: ' + id

Given path id
# When method get
# Then status 200
# And match response contains user
```

# テストスクリプト（featureファイル）の書き方

テストスクリプトの書き方については、主に以下の内容を参照してください。

- https://github.com/intuit/karate#syntax-guide

## 基本構造

基本的な構造は、以下のようになります。

```cucumber
Feature: テストの概要
    テストの詳細内容の記述。
    複数行で記述可能。

Background:
# （optional)
# 全シナリオで共通的に利用するグローバル変数などを定義する。
# 各シナリオの前に実行される。

Scenario: シナリオの概要
# テストケースの記述
# 
# Given-When-Then-And -> テストの振る舞いの記述
# * def               -> 変数の定義
# * print             -> コンソールへの出力

Scenario: シナリオの概要
# テストケースの記述
```

## テストシナリオ

### Given-When-Then-And
テストシナリオの基本構文となるテストステップの記述です。
ひとつの ```Scenario:``` の中に、複数記述することができます。

- Given
  - オブジェクトの作成や設定など、前提条件を記述する。
- When
  - イベントやアクションを記述する。REST-APIの呼び出しなどを行う部分。
- Then
  - 期待される結果を記述する。アサーションを使用し、実際の結果と記述した期待結果を比較する。
- And
  - 直前のステップと同じ意味をもつ。複数の定義や条件を連続して記述する際に利用する。

以下のシナリオの内容を見てみます。
このシナリオでは、 [JSONPlaceholder](https://jsonplaceholder.typicode.com/) のAPIを利用しています。

```cucumber
Scenario: get all users and then get the first user by id

Given path 'users'
When method get
Then status 200

* def first = response[0]

Given path 'users', first.id
When method get
Then status 200
```

Backgroundで、```* url 'https://jsonplaceholder.typicode.com'``` と記述されているため、最初のテストステップでは、以下の内容を実行していることになります。

```
(1)Given path 'users'
(2)When method get
(3)Then status 200
```

- (1) ```https://jsonplaceholder.typicode.com/users``` のAPI（userの一覧取得）をテストする。
- (2)```HTTP GET``` でアクセスする。
- (3)```HTTPステータス２００``` であることを確認する。

次のテストステップでは、前ステップのレスポンスを利用して、以下の内容を実行しています。

```
(1)* def first = response[0]

(2)Given path 'users', first.id
(3)When method get
(4)Then status 200
```
- (1)一覧取得のレスポンスから、最初の１件を取得する。
- (2)```https://jsonplaceholder.typicode.com/users/{id}``` のAPIをテストする。
  - このとき、(1)で取得したデータの ```id``` をパラメータとして指定する。
- (3)```HTTP GET``` でアクセスする。
- (4)```HTTPステータス２００``` であることを確認する。

このようにして、テストシナリオを記述することができます。

また、以下のように、変数を指定してAPIの実行をすることも可能です。

```cucumber
* def user =
"""
{
  "name": "Test User",
  "username": "testuser",
  "email": "test@user.com",
  "address": {
    "street": "Has No Name",
    "suite": "Apt. 123",
    "city": "Electri",
    "zipcode": "54321-6789"
  }
}
"""

Given url 'https://jsonplaceholder.typicode.com/users'
And request user
When method post
Then status 201
```


### 拡張
example の内容にはありませんが、テストスクリプトのファイル内では、JavaScriptで関数を定義したり、さらにその中でJavaのクラスを呼び出したりすることができます。
これにより、多少複雑な処理が伴ったり、対象アプリの処理を実行したりする場合でも、テストシナリオを完結に記述することが可能になります。

例えば、現在日時を基準に、動的に日時を示す文字列を取得したい場合、以下のような記述で可能です。

```
* def getTime =
"""
function(min) {
  var dateFormatType = Java.type('java.text.SimpleDateFormat');
  var dateFormat = new dateFormatType("yyyy-MM-dd'T'HH:mm:ss.SSS+09:00");
  var date = new java.util.Date();
  date.setTime(date.getTime() + min*60*1000);
 
  return dateFormat.format(date);
}
"""

* def timestamp = {"timestamp": #(getTime(0))}
* print timestamp
```

これらの機能を使うことで、テストケースを効率よく作成していくことができそうですね！

詳細は、こちらを参照。

- https://github.com/intuit/karate#javascript-functions


# まとめ
example の内容を元に、Karateのテストの内容がどのような構成・記述になっているのかを確認しました。
テストシナリオの内容も、直感的に理解できる内容だと感じます。

Karateの機能は、ここに書いただけでなく、[README](https://github.com/intuit/karate/blob/master/README.md) の内容を見ると、他にもいろいろとあるようです。
その辺りは、別途、確認していきたいと思っています。