APIのE2Eは `カラテ` を使うべし。古事記にもそう書いてある。
<これまでのあらすじ>
API開発を終えたサラリマンエンジニアであるタロウは、しめやかにテストを開始した。しかし、そこに待ち受けたのはジェーソン(訳注: JSONのことだと思われる)の複雑怪奇極まりない組み合わせだった。納期を守らないエンジニアは実際シツレイにあたりセプクものだ。このままではジリー・プアー(訳注:徐々に不利)であるタロウの前に恩師であるカラテ・マスターが現れる。
TL;DR
- APIのユニットテスト、E2Eテストには Karateが便利。
- 使い方はクセがあるけど、使えば使うほどよく馴染んでくる。
- <>で囲まれたタイトル内は茶番なので無視していいよ。
- この記事は ただの集団 Advent Calendar 2020 - Adventarの18日目の記事です。
<インストラクション・ワン>
「ここに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 test
で BUILD SUCCESS
が出れば成功です。
<カラテ・ウィズ・キュウリ>
「カラテを使ったテスト記述には独特のジツがある。キュウリだ(訳注: Cucumber BDDのこと。テストツールの1つ)」
「アィェェェェ・・・キュウリ?キュウリナンデ?」
「正確には、キュウリで採用されるGherkin(ガーキン)形式で記述をするのだ。これが実に奥ゆかしい」
テストシナリオ
作成したプロジェクトの中を見てみましょう。IntelliJやvscodeなどのエディタで開くのがおすすめです。
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
- 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
プロジェクトの構造
以下のフォルダ構造になっていると思います。
> 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
- パフォーマンステスト - Karate Gatling - Karate
- Scala製の性能テストツールであるgatlingと連携してシナリオを利用したパフォーマンステストを実行できます。
- featureファイルの再利用
- テストデータをファイルや別で用意し、別の1件用のテストシナリオにパラメータとして渡して実行できます。https://intuit.github.io/karate/#data-driven-features
- データとテストシナリオが分離できるのと、シナリオを組み合わせた別シナリオを構築できたりします。https://intuit.github.io/karate/#calling-other-feature-files
その他、トリッキーな使い方としてurlを書き換えながら複数のAPIの結合テストもできたりします。 - API-1の実行結果をもとに、API-2にリクエストを投げ、API-3で結果を確認するなど(本当にそのテストケースが必要かどうか不明ですが)
実行時の注意
- IntelliJ, VS CodeにはKarate用プラグインが有り、フォーマッターや実行のサポートがあるので使ってみてください。(使い方は省略します) IDE Support · intuit/karate Wiki · GitHub
- Nashorn関係の実行エラーが出る場合、JDKのバージョンが新しすぎる or 古すぎる可能性を疑ってください。
あとがき
ネタが古いのはご容赦を。kareteを見たときに思い浮かんだのが、ファ◯コンソフトのカラテカとニン◯ャスレイヤーだったので・・・ (途中で疲れたので内容としては中途半端で面白くないかもしれません。)
真面目な情報としては、英語で大変かもしれませんが公式サイトが一番まとまっているし正しい情報なのでこちらを参考にしてください。 intuit.github.io
マッポーめいたコロナ禍ですが来年は良い年になりますように。Wasshoi!