Karate

Contents

Karateとは

KarateはWeb API用のテストフレームワークである。

Gherkin(ガーキン)形式(featureファイル)でテストケースを記述できるので、ビルド不要であり、また、同じAPIに対して複数のテストケースを作成する場合に便利である。

JVM上で動作する。必要に応じてテストケース中にJavaコードやJavaScriptコードを埋め込むことが可能。

負荷試験ツールであるGatlingと連携して負荷試験も可能である。

テスト結果のレポートはHTMLとして出力可能。

公式サイト:https://github.com/intuit/karate

 

紹介資料

 

仕様説明書

https://intuit.github.io/karate/

 

環境構築

Javaのインストール

JREの1.8.0_112以上が必要である。※ただし、最新版のJavaではJRE単体では配布されず、JDKにバンドルされるので、JDKをインストールする

今回はOpen JDK 12をインストールする

 

Apacheのインストール

テスト実行結果はターミナルにも出力されるが、HTMLとしてレポートにも出力される。このHTMLはテスト実行のたびに上書きされてしまうが、HTMLのほうが見やすいので、Webサーバーを用意してWebブラウザでみられるようにしたほうが良い。

Webサーバーは何でもよいが、今回はApacheとした。

こちらを参考にApacheのインストールを行う

 

Karateのインストール

KarateはMavenやGradleを使用したデプロイ、またはjarファイルの配置によるインストールが可能である。

 

Mavenによるビルド

Mavenは事前にインストールしておくこと

  1. ソースコードをダウンロードする
    curl -L -s https://github.com/intuit/karate/archive/master.zip -o karate-master.zip
  2. 展開する
    unzip karate-master.zip
    cd karate-master
  3. ビルドする
    mvn compile
  4. ビルドしたファイルを移動する

 

jarファイル配置によるインストール

この方法では、gatlingによる負荷試験が行えないが、gatlingは別途インストールできる。

  1. 最新版のビルドファイルのURLを確認する
    https://dl.bintray.com/ptrthomas/karate/
    ※URL中のパスにコロン(:)が入っている場合、除外すること
  2. jarファイルをダウンロードする
    curl -s -L -O https://dl.bintray.com/ptrthomas/karate/karate-0.9.4.jar
  3. インストール場所に移動する
    mkdir /usr/local/karate
    mv karate-* /usr/local/karate/
  4. リンクを作成する
    ln -s /usr/local/karate/karate-0.9.4.jar /usr/local/karate/karate.jar
  5. エイリアスを設定する
    alias karate='java -jar /usr/local/karate/karate.jar'
  6. エイリアスは永続化しておく
    echo "" >> /etc/bashrc
    echo "# KARATE ENVIRONMENT VARIABLE" >> /etc/bashrc
    echo "alias karate='java -jar /usr/local/karate/karate.jar'" >> /etc/bashrc
  7. 実行できることを確認する
    karate -h
    実行例
    07:53:17.835 [main] INFO  com.intuit.karate.Main - Karate version: 0.9.4
    Usage: <main class> [-hsu] [-c=<cert>] [-e=<env>] [-k=<key>] [-m=<mock>]
    [-n=<name>] [-o=<output>] [-p=<port>] [-T=<threads>]
    [-t=<tags>]... [<tests>...]
    [<tests>...] one or more tests (features) or search-paths to run
    -c, --cert=<cert> ssl certificate (default: cert.pem)
    -e, --env=<env> value of 'karate.env'
    -h, --help display this help message
    -k, --key=<key> ssl private key (default: key.pem)
    -m, --mock=<mock> mock server file
    -n, --name=<name> scenario name
    -o, --output=<output> directory where logs and reports are output (default
    'target')
    -p, --port=<port> mock server port (required for --mock)
    -s, --ssl use ssl / https, will use 'cert.pem' and 'key.pem' if
    they exist in the working directory, or generate them
    -t, --tags=<tags> cucumber tags - e.g. '@smoke,~@ignore'
    -T, --threads=<threads> number of threads when running tests
    -u, --ui show user interface

 

Hello World

簡単な動作の確認を行う

モックの用意

モックはデモ用のものを使用する

  1. 任意のパスにfeatureファイルを作成する
    vi hello-world-mock.feature
    ファイルの内容を張り付けて保存する
    Feature: test hello world

    Scenario: pathMatches('/hello')
    * def message = 'Hello World'
    * def response = { message: '#(message)' }
  2. モックサーバーを起動する
    karate -m hello-world.feature -p 8080
    ※カレントディレクトリにtargetディレクトリが作成され、その中にログファイルが生成されるので注意

 

テストケースの用意

モック起動中はターミナルがブロックされるので、新しいターミナルを開く。

  1. 任意のパスにfeatureファイルを作成する
    vi hello-world-test.feature
    ファイルの内容を張り付けて保存する。成功するシナリオ、失敗するシナリオを定義している。
    Feature: test hello world

    Scenario: test /hello
    When url 'http://localhost:8080/hello'
    And method get
    Then status 200
    And def message = 'Hello World'
    And match response == { message: '#(message)' }

    Scenario: test /hello fail
    When url 'http://localhost:8080/hello'
    And method get
    Then status 200
    And def message = 'Hello World XXX'
    And match response == { message: '#(message)' }
  2. テストを実行する
    karate hello-world-test.feature
    実行例
    シナリオのうち、1つが成功、1つが失敗している。
    [root@server path]# java -jar /usr/local/karate/karate.jar hello-world-test.feature
    06:08:23.907 [main] INFO com.intuit.karate.Main - Karate version: 0.9.4
    Warning: Nashorn engine is planned to be removed from a future JDK release
    06:08:26.037 [ForkJoinPool-1-worker-3] DEBUG com.intuit.karate - request:
    1 > GET http://localhost:8080/hello
    1 > Accept-Encoding: gzip,deflate
    1 > Connection: Keep-Alive
    1 > Host: localhost:8080
    1 > User-Agent: Apache-HttpClient/4.5.5 (Java/12.0.2)

    06:08:26.073 [ForkJoinPool-1-worker-3] DEBUG com.intuit.karate - response time in milliseconds: 32.60
    1 < 200
    1 < Content-Type: application/json
    {"message":"Hello World"}

    06:08:26.124 [ForkJoinPool-1-worker-3] DEBUG com.intuit.karate - request:
    1 > GET http://localhost:8080/hello
    1 > Accept-Encoding: gzip,deflate
    1 > Connection: Keep-Alive
    1 > Host: localhost:8080
    1 > User-Agent: Apache-HttpClient/4.5.5 (Java/12.0.2)

    06:08:26.139 [ForkJoinPool-1-worker-3] DEBUG com.intuit.karate - response time in milliseconds: 13.71
    1 < 200
    1 < Content-Type: application/json
    {"message":"Hello World"}

    06:08:26.145 [ForkJoinPool-1-worker-3] ERROR com.intuit.karate - assertion failed: path: $.message, actual: 'Hello World', expected: 'Hello World XXX', reason: not equal
    06:08:26.317 [pool-1-thread-1] INFO com.intuit.karate.Runner - <<fail>> feature 1 of 1: hello-world-test.feature
    ---------------------------------------------------------
    feature: hello-world-test.feature
    report: target/surefire-reports/hello-world-test.json
    scenarios: 2 | passed: 1 | failed: 1 | time: 0.6579
    ---------------------------------------------------------
    Karate version: 0.9.4
    ======================================================
    elapsed: 2.29 | threads: 1 | thread time: 0.66
    features: 1 | ignored: 0 | efficiency: 0.29
    scenarios: 2 | passed: 1 | failed: 1
    ======================================================
    failed features:
    hello-world-test: hello-world-test.feature:15 - path: $.message, actual: 'Hello World', expected: 'Hello World XXX', reason: not equal

    Exception in thread "main" picocli.CommandLine$ExecutionException: there are test failures
    at com.intuit.karate.Main$1.handleExecutionException(Main.java:133)
    at picocli.CommandLine.parseWithHandlers(CommandLine.java:1157)
    at com.intuit.karate.Main.main(Main.java:139)

 

テストケース文法

テストケースはGherkin(ガーキン)形式

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

 

テストケースの構成

テストケースはセクションを定義し、その中にシナリオとして各テストケースを、その中にステップとして各処理内容を定義する構成である。

Feature: brief description of what is being tested
    more lines of description if needed.

Background:
  # this section is optional !
  # steps here are executed before each Scenario in this file
  # variables defined here will be 'global' to all scenarios
  # and will be re-initialized before every scenario

Scenario: brief description of this scenario
  # steps for this scenario

Scenario: a different scenario
  # steps for this other scenario

 

セクション

テストケースはいくつかのセクションに分かれる。

セクション中に具体的なテスト内容(ステップ)を定義することになる。

セクションの書式は次の通り

<セクション名>: <セクション内設定>
<セクション内設定>
...

 

なお、一度のテスト実行ですべてのセクションが実行されるので、テストを分けたい場合は別のfeatureファイルに記述する。

 

Feature

このファイルの説明文を記述する。テスト内容に影響を与えないコメントである。

このセクションはファイルの冒頭に1つ定義する。

Background

各シナリオセクションの実行前に共通して実行する内容を定義する。

このセクションはオプションであり、定義を省略可能である。

 

Scenario

個別のテスト内容を定義する。

各シナリオはシナリオ名で特定される。

 

ステップ

ステップはテスト内の1命令を表す。

ステップの冒頭に以下のステップ接頭辞のいずれかを記述する。

各ステップ接頭辞に違いは無く、任意の使い分けをするとよい。使い分けをしないのであれば、アスタリスクに統一すればよい。

一般的な使い分けは以下である。

参考:https://martinfowler.com/bliki/GivenWhenThen.html

 

ステップ式

全般

def

変数に値を設定する。定義した変数はほかの式で使用可能になる。

https://github.com/intuit/karate#def

 

変数を参照する際は次のように記述する。

変数を文字列と連結したり、substringメソッドで変形したりする場合、’#(<変数名>)’の形態ではできない。従って、JSONに変形した文字列を格納したい場合は一旦別の変数に格納しなおす必要がある。

* def a = 'a'
* def b = a+'b'
* def j1 = {val:#(ab)}
# 誤り
* def j2 = {val:a+'b'}
* def j3 = {val:#(a)+'b'}

 

print

ログに任意の文字列を出力する

https://github.com/intuit/karate#print

 

replace

文字列の置換を行う

https://github.com/intuit/karate#replace

 

yaml

yaml形式のテキストをJSONオブジェクトに変換する。

yamlの状態ではオブジェクトではなく、あくまでテキストとして扱われる。

https://github.com/intuit/karate#yaml

 

csv

csv形式のテキストをJSONオブジェクトに変換する。

https://github.com/intuit/karate#csv

 

read

ファイルを読み込む

https://github.com/intuit/karate#reading-files

 

 

型変換

https://github.com/intuit/karate#type-conversion

 

configure

テストごとの個別の環境設定を行う。

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

 

call

JavaScriptの関数を呼び出す。他のfeatureファイルで定義した関数も呼び出せる。

https://github.com/intuit/karate#call

 

テストの振る舞い

url

テスト対象のURLを設定する

https://github.com/intuit/karate#url

 

path

テスト対象のパスを設定する。パスはurl式でも設定可能である。

複数回呼び出すと、最後に文字列が連結される。

https://github.com/intuit/karate#path

request

POSTする際に送信するリクエストを記述する。

https://github.com/intuit/karate#request

 

method

HTTPメソッドを指定する。

使用できるメソッドは次の通り。

https://github.com/intuit/karate#method

 

status

テスト結果のステータスコードをチェックする。

https://github.com/intuit/karate#status

 

param / params

クエリストリングのパラメータを指定する。

https://github.com/intuit/karate#param

https://github.com/intuit/karate#params

 

header / headers

HTTPヘッダの値を設定する。

https://github.com/intuit/karate#header

https://github.com/intuit/karate#headers

 

cookie / cookies

送出するCookieを指定する。

https://github.com/intuit/karate#cookie

https://github.com/intuit/karate#cookies

 

form field

HTMLフォームからデータを送信する際に行われるURLエンコードを行ってデータ送信する。

https://github.com/intuit/karate#form-field

multipart file

送信するファイルを指定する

https://github.com/intuit/karate#multipart-file

 

retry until

レスポンスが毎回変わりうる際に、特定の条件を満たすまでHTTPリクエストを繰り返す。

最大のリトライ回数はパラメータretryで設定可能。

https://github.com/intuit/karate#retry-until

 

テスト結果の確認

assert

特定の条件を満たすかを検査する。

検査式にはTrueになる条件を記載する。

単純な値の比較のみしかできず、JSONのような構造体の一括の比較の場合はmatch式を使用する。

https://github.com/intuit/karate#assert

 

match

JSON、XMLの構造体の内部の値を一括して比較する。

https://github.com/intuit/karate#match

 

比較演算子

比較演算子は==以外にも使用できるものがある。

 

マーカー

マーカーとして値のパターンとして比較できる。
例えば次のように指定することで、値の内容にかかわらず、文字列型かどうかをチェックされる。

* match myJson == { foo: '#string' }
使用可能なマーカー
Marker Description
#ignore Skip comparison for this field even if the data element or JSON key is present
#null Expects actual value to be null, and the data element or JSON key must be present
#notnull Expects actual value to be not-null
#present Actual value can be any type or even null, but the key must be present (only for JSON / XML, see below)
#notpresent Expects the key to be not present at all (only for JSON / XML, see below)
#array Expects actual value to be a JSON array
#object Expects actual value to be a JSON object
#boolean Expects actual value to be a boolean true or false
#number Expects actual value to be a number
#string Expects actual value to be a string
#uuid Expects actual (string) value to conform to the UUID format
#regex STR Expects actual (string) value to match the regular-expression ‘STR’ (see examples above)
#? EXPR Expects the JavaScript expression ‘EXPR’ to evaluate to true, see self-validation expressions below
#[NUM] EXPR Advanced array validation, see schema validation
#(EXPR) For completeness, embedded expressions belong in this list as well

set

JSON内部の特定の値を変更する

https://github.com/intuit/karate#set

 

 

値はJavaScriptと同様の記載方法で定義できる。

つまり、数値はそのまま、文字列はダブルクォーテーションかシングルクォーテーションで囲う。

 

構造体

JSON形式やXML形式などの構造をなす値もそのまま値として記述可能である。

 

文字列の連結

構造体のみ値内で文字列の連結が可能である。

ヒアドキュメント

値が複数行にわたって記述したい場合、次のようにして記述することができる。

 

構造体の無視

JSON形式やXML形式で記述すると自動的にその構造体として扱われるが、構造体としてではなく、テキストとして扱いたい場合は特別な対応が必要である。

text式をdef式の代わりに使用する。

JSON形式で同じフィールドを持つオブジェクトが複数続く場合、表形式として定義可能である。

https://github.com/intuit/karate#table

 

特別な値

https://github.com/intuit/karate#special-variables

response

HTTP実行結果のレスポンスが格納される

https://github.com/intuit/karate#response

 

responseCookies

HTTP実行で返されたCookieが格納される

https://github.com/intuit/karate#responsecookies

 

responseHeaders

HTTP実行で返されたヘッダーが格納される

https://github.com/intuit/karate#responseheaders

 

responseStatus

HTTP実行で返されたステータスコードが格納される

https://github.com/intuit/karate#responsestatus

 

karate

Karate環境を操作するオブジェクトが格納される

https://github.com/intuit/karate#the-karate-object

 

その他

コメント

#で始まる行はコメントとして扱われる。

行の途中からコメントにはできない。

 

三項演算

三項演算を使用できる。

 

環境設定ファイル

Karateでは環境設定ファイルを用意し、テスト環境に対しての設定を行うことができる。

設定では、各テストケースで共通に使用できるパラメータや、Karate環境に対する設定を行うことができる。

設定ファイルはファイル名”karate-config.js”として作成し、内部にはJavaScript形式でJSONオブジェクトを返す関数を1つ定義しなければならない。

その返り値のJSON中でパラメータを設定する。

karate-config.jsファイルはclasspathに配置する必要がる。カレントディレクトリをクラスパスに追加する設定をしているなら、karateの実行ディレクトリに配置しておけばよい。

参考:https://github.com/intuit/karate#configuration

 

設定サンプル

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

 

環境設定

設定ファイルで定義する関数ではkarate変数が暗黙の変数として使用できる。

karateオブジェクトではいくつかのメソッドを持つが、configureメソッドではKarate環境に対する設定を行える。

なお、テストケース内でも個別に設定を上書きすることが可能である。

参考:https://github.com/intuit/karate#configure

設定したパラメータは変数として参照できる

 

負荷試験(Gatling連携)

参考:https://github.com/intuit/karate/tree/master/karate-gatling

 

インストール

GatlingはMavenなどで自身でソースコードからビルドした場合はjarファイルに含まれているが、ビルド済みのjarファイルをダウンロードしてKarateをインストールした場合は含まれていない。

この場合は別途インストールする。なお、インストールはデモ用のリポジトリを使用するのが簡単である。

参考:https://github.com/ptrthomas/karate-gatling-demo

  1. Mavenが必要なので、事前にインストールしておくこと
  2. インストールするディレクトリを作成する
    mkdir /usr/local/gatling
    cd /usr/local/gatling
  3. 簡単にアクセスできるように環境変数を設定する
    export GATLING_HOME=/usr/local/gatling
  4. 環境変数を再起動後も有効にする
    echo "" >> /etc/bashrc
    echo "# GATLING ENVIRONMENT VARIABLE" >> /etc/bashrc
    echo "export GATLING_HOME=$GATLING_HOME" >> /etc/bashrc
  5. ソースコードなどをダウンロードする
    curl -s https://codeload.github.com/ptrthomas/karate-gatling-demo/zip/master -o karate-gatling-demo-master.zip
  6. アーカイブを展開する
    unzip karate-gatling-demo-master.zip
    rm -f karate-gatling-demo-master.zip
  7. プロジェクトをコピーして使うためのテンプレートを作成する
    1. デモプロジェクトをコピーする
      cp -r karate-gatling-demo-master template
    2. 不要なファイルを削除する
      rm -f template/README.md
      rm -f template/src/test/java/mock/*feature
      rm -f template/src/test/java/mock/CatsGatlingSimulation.scala
      rm -f template/.gitignore
    3. テスト実行用ファイルを標準的な名前に変更する
      mv template/src/test/java/mock/CatsKarateSimulation.scala template/src/test/java/mock/StartGatlingWithKarate.scala
      sed "s/CatsKarateSimulation/StartGatlingWithKarate/" -i template/src/test/java/mock/StartGatlingWithKarate.scala
      sed "s/CatsKarateSimulation/StartGatlingWithKarate/" -i template/pom.xml
    4. 実行レポートの出力場所を共通化する。
      レポートはHTMLで出力されるため、共通化してそのパスをWebサーバーで公開するのが良い。
      mkdir report
      mkdir template/target/
      cd template/target/
      ln -s /usr/local/gatling/report gatling 
    5. Apacheで公開設定を行う
      vi /etc/httpd/conf/httpd.conf
      以下のように公開する
      Alias /gatling "/usr/local/gatling/report"
      <Directory "/usr/local/gatling/report"> AuthType Digest
      AuthName html
      AuthUserFile conf/auth
      AuthGroupFile conf/auth
      Require valid-user
      Options Indexes
      AllowOverride None
      </Directory>
    6. Apacheの設定を反映する
      systemctl restart httpd
  8. デモ用のテストを実行する場合は、次を行う。実施しなくてもよいが、実施すると、問題なくビルドできているかを確認できる。
    1. デモ用ディレクトリに移動する
      cd karate-gatling-demo-master/
    2. ビルドする。
      ※テスト用のコードとして配置されているので、フェーズ”test-compile”を使用する。
      mvn test-compile
    3. テストを実行する
      mvn gatling:test

 

デモプロジェクトについて

デモプロジェクトのテストコードを改修することで、自身のテストをgatlingに移行しやすい。

また、モック付きのテスト用のコードがgatling自体にも添付されている。
https://github.com/intuit/karate/tree/master/karate-gatling/src/test

これを改修したデモプラグラムは上記の改修版なので、両者を比較すれば、自分のテストコードで負荷試験を行う際の改修箇所・方法について理解しやすい。
https://github.com/ptrthomas/karate-gatling-demo/tree/master/src/test/java

 

デモプロジェクトの構成

ダウンロードしたkarate-gatling-demo-masterには以下のファイルが含まれる

 

自身で改修する際に使用するには以下である。

 

テストコード改修

作成したテンプレートプロジェクトを元にすれば簡単に各テストシナリオ毎のgatlingプロジェクトを作成できる。

  1. テンプレートをコピーする
    cd $GATLING_HOME
    cp -r template <任意のプロジェクト名>
    cd <任意のプロジェクト名>
  2. モックが不要であれば、モックプログラムは削除する
    rm -f src/test/java/mock/MockUtils.java
  3. 使用したいkarate-config.jsがあれば、コピーする。なければスキップ。
    cp <任意のパス> src/test/java/karate-config.js
  4. 用意したfeatureファイルをコピーする
    cp <任意のパス> src/test/java/mock/
  5. テスト実行内容を記述する
    詳細は以下「テスト実行ファイルの記述方法」を参考
    vi src/test/java/mock/StartGatlingWithKarate.scala
  6. テストを実行する
    mvn test-compile gatling:test
    ※テスト再実行の場合
    mvn gatling:test

 

テスト実行ファイルの記述方法

karateProtocolの生成

karateProtocolはkarateによりテスト実行する対象パスを宣言する。これにより、宣言したパスの各HTTPメソッド単位でレポートが出力される。パスは”{}”で囲うことで、集約できる。

pauseForにより、各HTTPリクエストごとの、待機時間(ミリ秒)を指定できる。待機時間を設定しない(Nilにする)場合は待機せずにすぐに次のリクエストを送信する。
なお、パスの指定は完全一致で行う。

下記の場合、pauseForではパス”/cats“はgetメソッドは15ミリ秒待機し、postメソッドは25ミリ秒待機する。
レポートは/cats/{id}“のget、post、delete、putと”/cats”のget、post、delete、putのそれぞれで集計される。

 val protocol = karateProtocol(
   "/cats/{id}" -> Nil,
   "/cats" -> pauseFor("get" -> 15, "post" -> 25)
 )

 

nameResolverの設定

nameResolverは出力レポートをさらに分離したい際に使用する。nameResolverの設定は任意である。

protocol.nameResolver = (req, ctx) => req.getHeader("karate-name")

 

シナリオの生成

使用するシナリオ(featureファイル)を指定する。
scenarioメソッドの引数の文字列は任意の文字列で、特に意味はない。
参考:https://gatling.io/docs/2.3/general/scenario/

下記の「@name=delete」の記述は、featureファイルの複数のシナリオのうち、実行するものを指定している。

val create = scenario("create").exec(karateFeature("classpath:mock/cats-create.feature"))
val delete = scenario("delete").exec(karateFeature("classpath:mock/cats-delete.feature@name=delete"))

cats-delete.featureファイルの抜粋

~~~
Scenario: this scenario will be ignored because the gatling script looks for the tag @name=delete
* print 'this should not appear in the logs !'
When method get
Then status 400

@name=delete
Scenario: get all cats and then delete each by id
~~~

 

テストの実行

定義した方法でテストを実行する。実行方法はinjectメソッドで指定し、複数方法でテストする場合は、コンマ区切りで複数記述する。

テスト方法は以下が指定できる。なお、時間は単位を省略可能で、その場合は単位は秒となる。分の場合はminutesを指定する。明示的に秒を指定する場合はsecondsを指定する。

現在のバージョンではいくつか使用できないものがあった。

参考:https://gatling.io/docs/2.3/general/simulation_setup#injection

参考:https://mussyu1204.myhome.cx/wordpress/it/?p=397

例:

setUp(
  create.inject(rampUsers(10) during (5 seconds)).protocols(protocol),
  delete.inject(rampUsers(5) during (5 seconds)).protocols(protocol)
)