karateでREST APIのテストをする。REST-Assuredとのソース比較も

# はじめに
Web UIとREST APIのテストフレームワークを探しているときに[karate](https://intuit.github.io/karate/)という、とても興味のそそられるフレームワークを見つけたので使い方や便利なところをまとめます。

# 環境
 - IntelliJ：2020.1.3
 - Java:11
 - Gradle:7.0

# 環境作成
## IntelliJのインストール
[公式のホームページ](https://www.jetbrains.com/ja-jp/idea/download/#section=windows)からインストーラーをダウンロードしてインストールしてください。

## プロジェクトの作成
ファイルタブ > 新規 > プロジェクト を選択してプロジェクト作成ウィンドウを開いてください。


Gradle > Java　を選択してJavaプロジェクトの作成


これでプロジェクトの作成が完了しました。


## 必要なライブラリの追加
build.gradleにコンパイル時のkarateの追加とJUnit platform を使う設定をします。

```
dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
}
```

を下のように修正してください。

```
test {
    useJUnitPlatform()
}

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    testCompile 'com.intuit.karate:karate-junit5:1.0.1'
    testImplementation('org.junit.jupiter:junit-jupiter')
}
```

## スクリプトパスの追加
featureファイルを読み込ませるために、テストパスの場所を追加します。

```
sourceSets {
    test {
        resources {
            srcDir file('src/test/java')
            exclude '**/*.java'
        }
    }
}
```

# テストソースの作成
karateは、テスティングフレームワークと組み合わせて使用します。今回はJUnit5と組み合わせます。
基本的にJUnitがJavaファイルを認識して、Javaファイルからテスト内容の書かれているfeatureを読み取りテストを実行します。
## Javaファイルの作成
JUnitから読み込まれるJavaファイルを作成します。

``` java
package com.karate.api;

import com.intuit.karate.junit5.Karate;

// JUnitに読み込ませる関数を定義
class PetsTest {
    @Karate.Test
    Karate petsTest() {
        // pets.featureを読み取るように定義
        return Karate.run("pets").relativeTo(getClass());
    }
}
```

## featureファイルの作成
実際のテストコードをfeatureファイルに記載していきます。ファイルの場所は上のクラスファイルと同じディレクトリにしてください。今回は例としてhttp://localhost:8081/Petsのテストをしています。

``` feature
Feature: sample karate test script
  # ここにテスト対象のURLを指定
  Background:
  * url 'http://localhost:8081'

  Scenario: pets get request
    # urlのパスを指定
    Given path 'Pets'
    # GETメソッドを定義
    When method get
    # 戻り値がhttpステータス200であることのチェック
    Then status 200
    # 戻り値のデータのチェック
    And match $ == [{"name":"pochi", "age": 2, "kind": "dog"}]

  Scenario: pets get request add param

    Given path 'Pets'
    # queryパラメータの設定。/Pets?kind=cat&name=tama
    And param kind = 'cat'
    And param name = 'tama'
    When method get
    Then status 200
    And match $ == [{"name":"tama", "age": 4, "kind": "cat"}]

    Given path 'Pets'
    # queryパラメータの設定。/Pets?kind=dog&name=tama
    And param kind = 'dog'
    And param name = 'tama'
    When method get
    Then status 200
    And match $ == [{"name":"tama", "age": 2, "kind": "dog"}]

  Scenario: pets post request

    Given path 'Pets'
    # リクエストボディの設定
    And request [{"name":"pochi", "age": 2, "kind": "dog"}]
    # POSTメソッドを定義
    When method post
    Then status 200
    And match $ == [{"name":"pochi", "age": 2, "kind": "dog"}]
```

# テストの実行
この2つでテストのソースを書くことが出来ました。
さっそく実行してみます。実行はgradleコマンドからもできますが、せっかくなのでIntelliJのGUIからやってみます。

## Gradleウィンドウの表示
Gradleウィンドウが表示されていないときは、Gradleと書かれたところをクリックしてください。
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481713/70c8f2b3-4229-17f4-dd41-f37787b17b8e.png)

## テストの実行
Gradleウィンドウが表示されたらVertificationを開いてtestを選択してください。ここでtestがなかったり、エラーが出た場合はGradleの更新ボタンを押してください。

![無題2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481713/de3eb1e6-0bed-d4b3-bf1d-b56f25d6bfab.png)

## テストの結果確認
テストが正常に終了すると下のコンソールにpassと出るのでそれを確認します。

![無題３.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481713/c128acd2-bdd5-33e8-eccf-f0d8f9c39d6c.png)

## テストの個別結果の表示
上の例だとすべてのテスト結果が1つにまとめられていてわかりにくい場合もあるので、別の方法を紹介します。
IntelliJ上でソースやフォルダを右クリックして実行を選択するだけです。そうすることで下のように個別結果が表示されます。

![無題４.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481713/dce1a1cc-efc2-ea0e-3df9-d0d7767341bd.png)

さらに、プロジェクトルート/target/karate-reports配下にレポートが出力されています。

# REST-Assuredとのソースの比較
REST APIのテストライブラリで有名なものとしてはREST-Assuredがあります。これとkarateを比較してみて、karateがわかりやすいのか確認してみます。
## それぞれのソースコード
### REST-Assured
``` java
RestAssured.baseURI = "http://localhost:8081";

given()
    .when()
        .get("/Pets")
    .then()
        .statusCode(200)
        .body("name", equalTo("pochi"))
        .body("age", equalTo(2))
        .body("kind", equalTo("dog"));
```
### karate

``` Feature
Feature: sample karate test script

  Background:
  * url 'http://localhost:8081'

  Scenario: pets get request

    Given path 'Pets'
    When method get
    Then status 200
    And match $ == [{"name":"pochi", "age": 2, "kind": "dog"}]
```

## 感想
REST-Assuredは9行、karateは8行と書く量としてはそれほど変わりませんでした。ソースの見やすさとしてはJavaになれている人はREST-Assuredの方が見やすいのかなと思います。Javaになれていない人やインタフェースしか決めていない人にとってはkarateの方が見やすいのかなと思います。
REST-Assuredは関数の指定方法がJavaっぽい上に、いかにもプログラミングという見た目をしているのでJavaになれていない人にとっては取っつきにくいかと思います。一方でkarateは定義ファイルを記載するような見た目なのである程度システムを知っている人ならば取っつきやすいかと思います。

# 独自関数の使用
ここまでまとめて、featureとJavaでファイルが違うのでJavaで書いた独自関数を使用しにくいので純粋なJavaを使用したライブラリの方が良いという人もいるかもしれません。そこらへんもkarateは考慮していて簡単にJava関数をfeatureファイル内で使用することができます。

## Javaでの独自関数の作成
例としてDBにQueryを発行する関数を作成します。

``` java

package karate;

import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

public class DbUtils {

    private final JdbcTemplate jdbc;


    public DbUtils() {
        ResourceBundle bundle = ResourceBundle.getBundle("application");
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl(bundle.getString("spring.datasource.url"));
        dataSource.setUsername(bundle.getString("spring.datasource.username"));
        dataSource.setPassword(bundle.getString("spring.datasource.password"));
        jdbc = new JdbcTemplate(dataSource);
    }

    public Object readValue(String query) {
        return jdbc.queryForObject(query, Object.class);
    }

    public Map<String, Object> readRow(String query) {
        return jdbc.queryForMap(query);
    }

    public List<Map<String, Object>> readRows(String query) {
        return jdbc.queryForList(query);
    }
}
```

## featureからのJava関数の呼び出し
例としてDBにQueryを発行する関数を作成します。

```
 Background:
 * url 'http://localhost:8081'
 # 読み取るJavaのファイルを読み込む。karateフォルダ内のDbUtilファイルを読み込む
 * def DbUtils = Java.type('karate.DbUtils')
 # インスタンスを生成する
 * def db = new DbUtils()

  Scenario: pets post request

    Given path 'Pets'
    And request [{"name":"pochi", "age": 2, "kind": "dog"}]

    * def post_petId = response.id
    
    # SQL発行してDBから情報取得
    * def pets = db.readRows('SELECT * FROM Pets WHERE id="' + post_petId + '"')
    # DBとリクエストしたデータの比較
    And assert pets[0].name == "pochi"
    And assert pets[0].kind == "dog"
```

# 終わりに
開発をするときは、ある程度Javaのことを理解しているのでkarateをREST APIのテストだけに使う場合は、そこまでメリットを感じないです。どちらかと言うと個々の機能だけを抽出して使用するのではなく、REST API・モック・UIと複数のテストにkarateを導入することで総合的に学習コストを下げたり、ソース間の統一を図りきれいなソースにするというところを期待するのかなと思います。他にも人の入れ替えが多く、Javaに対する知識が少ない人が入ってくる可能性の高いプロジェクトだとテスト作成の人手としてJavaの知識がない人も考慮にいれることができるため、導入する意味はあるのかと思います。