APIのE2Eは `カラテ` を使うべし。古事記にもそう書いてある。

<これまでのあらすじ>

API開発を終えたサラリマンエンジニアであるタロウは、しめやかにテストを開始した。しかし、そこに待ち受けたのはジェーソン(訳注: JSONのことだと思われる)の複雑怪奇極まりない組み合わせだった。納期を守らないエンジニアは実際シツレイにあたりセプクものだ。このままではジリー・プアー(訳注:徐々に不利)であるタロウの前に恩師であるカラテ・マスターが現れる。

TL;DR

<インストラクション・ワン>

「ここに1つのAPIがある」「おヌシはこのAPIのテストを全て終わらせなければならん。おヌシならどうする?やってみせい」
「ハイ!センセ!エクセルでテストケースを管理し、ワタシのタイピング速度を持って終わらせます!」
「イヤーッ!」
「グワーッ!」
「なんたるウカツ!おヌシはエクセルをまるで方眼紙のように使ったりチェックリストのように使うというのか!そもそも実際のテストは人力とは!」 「ウゥ・・・返す言葉もありません」 「APIのテストには カラテ(訳注: karate。API用のテストツールを指す) を使え!実際役に立つ」
「ハイ!センセ!」
「これすなわちフーリンカザン!」

Karateとは

Karateは、APIテストの自動化、モック、パフォーマンステスト、さらにはUIの自動化までを1つの統一されたフレームワークにまとめることができる唯一のオープンソースのツールです。 (公式ドキュメントの日本語訳)

APIファーストだったり、マイクロサービスだったり、ソフトウェアエンジニアとしては日々何らかのAPIを開発したり運用したりしています。
とはいえ、日々増えていくAPIに対して、UTはともかくE2Eテストだったりパフォーマンステストのメンテナンスコストも膨大です。
安心してください。そこでカラテです。
見てください。このアイコン。
github.com

そうです。カラテカです。強いに決まっています。
(※カラテカについて知りたい方は こちら)

早速使い方を見てみましょう。

サンプルセットアップ

公式で紹介されているサンプルを使ってみます。
動作環境: - Java 1.8..112 以上 12以下 - ※Javaのセットアップは sdkman を利用すると便利です。 - Maven 3.3 以上 - ※macの場合、 brew install maven でセットアップ可能です。

mavenアーキタイプからプロジェクトを作成する機能を利用してプロジェクトを作成します。

## groupid, artifactIdは任意です
mvn archetype:generate \
-DarchetypeGroupId=com.intuit.karate \
-DarchetypeArtifactId=karate-archetype \
-DarchetypeVersion=0.9.6 \
-DgroupId=com.mycompany \
-DartifactId=myproject

作成されたフォルダーに移動し、 mvn testBUILD SUCCESS が出れば成功です。

<カラテ・ウィズ・キュウリ>

「カラテを使ったテスト記述には独特のジツがある。キュウリだ(訳注: Cucumber BDDのこと。テストツールの1つ)」
「アィェェェェ・・・キュウリ?キュウリナンデ?」
「正確には、キュウリで採用されるGherkin(ガーキン)形式で記述をするのだ。これが実に奥ゆかしい」

テストシナリオ

作成したプロジェクトの中を見てみましょう。IntelliJvscodeなどのエディタで開くのがおすすめです。

src/test/java/examples 配下にテストに必要なファイルが入っています。
src/test/java/examples/users/users.feature ファイルを開いてみましょう。

Feature: sample karate test script
  for help, see: https://github.com/intuit/karate/wiki/IDE-Support

  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

(以下略)

独特のアトモスフィアを感じさせるファイルですね。
これが BDDツールであるCucumberで採用されている Gherkin と呼ばれるフォーマットです。
ファイル内の構造は以下のとおりです。

## シャープから始めるとコメント業となります。
Feature: テスト概要などを記述します。
    複数行も可能です。

Background: 
    # このファイル内の各シナリオの前に実行されます。
    # JUnitでいう、`@Before`と同じ意味となります。
    # ここで定義された変数はグローバルスコープで各シナリオから利用できます。

Scenario: シナリオ名
    # テストシナリオ

Scenario: 別のシナリオ
    # 別のテストシナリオ

これを踏まえてサンプルを読み直すと・・・

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

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
  • Given path 'users'
    • https://jsonplaceholder.typicode.com/users というパスを対象とする(※urlはグローバル変数から補完される)
  • When method get
  • Then status 200
    • レスポンスのHTTPステータスは200であることを確認する

となります。Given-When-Thenで1テストケースとなる為、このシナリオは次のテストケースが存在します。シナリオ内はシーケンシャルに実行される為、テストの結果を次のテストに利用することも可能です。

    * def first = response[0]

    Given path 'users', first.id
    When method get
    Then status 200
    • def first = response[0]
  • レスポンスは配列なので、配列の1つ目をfirstという変数に代入
  • Given path 'users', first.id
    • https://jsonplaceholder.typicode.com/users/${first.id} というパスを対象(DSLのpathは、カンマ区切りでパスを繋げた指定となります)
  • When, Then: 最初のテストと同じ

文法をいくつか紹介

流れはなんとなく感じていただいたと思うので、具体的な文法をいくつか紹介します。Karateは非常に奥深いツールで独自のDSLをもっていますので、全部を紹介することはできませんが、いくつか抜粋して紹介します。

GIven

Givenステップでは主にHTTPリクエストの構築を行います。
Givenの後ろにDSLを加えることで構築します。

## url 指定
Given url 'https://jsonplaceholder.typicode.com'

## path指定。カンマ区切りで指定すると `/` でurlとすべての引数を結合してくれます。
And path 'users',  first.id

## 上記の場合、 Given url 'https://jsonplaceholder.typicode.com/users/${firist.id}' と同じ意味となります。

## param クエリーパラメータを指定
Given param  foo = 'bar'

## request リクエストのBodyを指定
Given request """
{
  "id" : 1
}
"""

## header ヘッダーを指定
Given header Accept = 'application/json'

## cookie クッキー指定
Given cookie foo = 'bar'

## form field htmlフォールによる値指定
Given form field name = 'foo'


# param, form field, header, cookie はそれぞれ params, form fields, headers, cookies とすることで json 指定もできます。

When

When ステップでは主にHTTPリクエストを実行します。 Whenの後ろにDSLを加えることで実行します。

## method 指定したHTTPメソッドを利用してリクエストを作成します。レスポンスは `response` オブジェクトに格納されます。
When method get
When method post 

Then

Thenステップでは主にアサーションを実行します。 Thenの後ろにアサーションメソッドを記述します。

## ==
Then match response == 'OK'

## !=
Then match response != 'NG'

## contains, contains deep レスポンスの一部だけ確認したい場合
Then match response contains {'a': 'x'}
Then match response contains deep {'a': 'x', 'b': [ {'b1': 1, 'b2': 2} ]} 


## Schema validation JSON-schemaのチェック
Then match response == '#[]'  

じつは・・・

Given, When, Then, And で挙動に違いが有りません。極端な話、ThenでHTTPリクエストを送ったり、Givenでassertしたり、順不同で記述することも可能です。
ですが、上記の説明に書いた通りの方針で順序よく記載したほうが読みやすくなりますので、文脈を意識して使い分けたほうが良いと思います。

DSLの一覧や詳しい仕様は以下のリンクから確認してください。 intuit.github.io

<データ・ドリブン・フューチャーズ>

「タロウ=サン。テストパターンは1つでは不十分。わかるな?」
「モチロンです」 「1つのパターンでダメなら、10のパターン。10のパターンでダメなら100のパターンを用意せよ。これぞ、データ・ドリブンのジツ」 (訳注: パターンが多ければいいというものでは有りません)

データ定義

テストパターン用のデータを用意し、データの数だけ同じテストシナリオを実行することができます。
大きく分けて2つの方法がありますが、ここではシナリオアウトラインを紹介します。

  ## Scenarioの代わりに、 Scenario Outline と記述します
  Scenario Outline: get exists user
    ## 変数は<var>形式で記述します
    Given path 'users', <id>
    When method get
    Then status 200
    And match $.username == '<username>'  ## $から始まる書式は responseをJSONとみなした変数取得のDSLです。

    ## テストデータをテーブル形式で用意します。1行目はカラム名で変数として利用できます。
    Examples:
      | id   | username  |
      | 1    | Bret      |
      | 2    | Antonette |
      | 3    | Samantha  |

これで、テストが3回実行されます。サンプルでImportした users.featureの一番下に追加して mvn test で試してみてください。
デバッグログで実際のリクエストが行われていることが確認できます。

レポート

デフォルトでレポートがHTMLで生成されます。ログやテスト結果が確認可能です。 > open target/surefire-reports/karate-summary.html

f:id:hajimeni:20201217174433p:plain

プロジェクトの構造

以下のフォルダ構造になっていると思います。

> tree
.
├── pom.xml
├── src
│   └── test
│       └── java
│           ├── examples
│           │   ├── ExamplesTest.java
│           │   └── users
│           │       ├── UsersRunner.java
│           │       └── users.feature
│           ├── karate-config.js
│           └── logback-test.xml
└── target

karateの実体はJUnitによるテスト実行です。
具体的にどのようにテストが実行されているかは、公式ドキュメントを参照してください。

https://intuit.github.io/karate/#junit-5

端的に説明すると

  • *.featureファイルだけでは実行されない
  • *.featureファイルを読み込んで実行する為のJavaのテストクラスが必要
  • *.featureファイルと1:1で対応させる必要はない。

となります。

その他の機能について

本当はいろんな機能をサンプル付きで紹介したいのですがそれを記述するには1ページ内に収めるべきものではないので、自分が使った事がある機能、便利そうな機能をピックアップして紹介します。

  • テストダブル - Karate Netty - Karate
    • MockAPIを構築することができます。
    • APIから別のAPIを呼び出しているなどしている際のMockサーバーをDSLで構築できます。
    • Karateのテスト対象をMockAPIにするとKarateでKarate(mock)のテストをすることになります
  • パフォーマンステスト - Karate Gatling - Karate
    • Scala製の性能テストツールであるgatlingと連携してシナリオを利用したパフォーマンステストを実行できます。
  • featureファイルの再利用

その他、トリッキーな使い方としてurlを書き換えながら複数のAPI結合テストもできたりします。 - API-1の実行結果をもとに、API-2にリクエストを投げ、API-3で結果を確認するなど(本当にそのテストケースが必要かどうか不明ですが)

実行時の注意

  • IntelliJ, VS CodeにはKarate用プラグインが有り、フォーマッターや実行のサポートがあるので使ってみてください。(使い方は省略します) IDE Support · intuit/karate Wiki · GitHub
  • Nashorn関係の実行エラーが出る場合、JDKのバージョンが新しすぎる or 古すぎる可能性を疑ってください。
    • 確認方法として、 java -version ではなく 環境変数$JAVA_HOME も疑ってください。
    • NashornはJDK8で導入され、15で廃止されました。現行のkarate(0.9.x)ではNashornを利用しており、Karateの公式対応しているJavaのバージョンも(1.8.0_11からJava12まで)となっています

あとがき

ネタが古いのはご容赦を。kareteを見たときに思い浮かんだのが、ファ◯コンソフトのカラテカとニン◯ャスレイヤーだったので・・・ (途中で疲れたので内容としては中途半端で面白くないかもしれません。)

真面目な情報としては、英語で大変かもしれませんが公式サイトが一番まとまっているし正しい情報なのでこちらを参考にしてください。 intuit.github.io

マッポーめいたコロナ禍ですが来年は良い年になりますように。Wasshoi!