SmartShoppingでプログラマをしております桑島です。
スマートショッピングではCTOとDevOpsチームを中心にエンジニア組織の生産性向上に取り組んでおり、その一環としてインテグレーションテスト導入のためツールの調査を行ないました。
インテグレーションテストについて検索してみると、Cucumberなどのフロントエンド側からテストする手法や、PostmanなどのAPIを直接実行する手法など考え方が様々のようですが、スマートショッピングではE2Eレベルのテストは別途行っているため、後者のツールについて検討しました。
PostmanについてはNewmanでコマンドラインから実行できることがわかっていましたが、Postmanから出力される定義ファイルの差分レビューなどに課題があるという記事が複数見られたので、対象から外しています。
インテグレーションテストツールを検索して上がってきた中から下記ツールについて調査しました。 どちらもGolang製でYAMLファイルでテストを定義するものになっています。
テストシナリオとしては、APIでデータを投入後、結果を取得して値が合っていることを確認することを想定しています。
当記事では例としてtrelloのAPIを実行・検証していきます(たまたま手元で使っていたのと、エンジニアなら使ったことがありそうなので)
trello自体やtrelloのAPIについてはサイトやドキュメントを参照してください。
ということで、trelloのlist内にcardを作成してみて、正しく作成できているか確認するシナリオをそれぞれのツールで書いてみます。
k1LoWさんが作ったツールで、基本的にはscenarigoと似ていますが、大きく違う特徴としては以下かなと思います。
早速ですが、導入から定義ファイル、実行までみていきます。
ここでは go install
していますがhomebrewなどでも導入可能です。
go install github.com/k1LoW/runn/cmd/runn@latest
テスト定義ファイルはYAMLです。curl、grpcurlのコマンドを解析して出力できるらしいのですが、今回はうまくいかなかったので手で書いていきます。
List記法とMap記法がありますが、テスト結果の再利用などMap記法のほうがやりやすそうなので、Map記法で書いていきます。記法の違いについてはこちらの図がわかりやすいです。
以下がテスト定義ファイルの全体になります。APIキー、トークン、listのIDについては実際はファイル中に直接書いていますが、適宜置き換えてください。
desc: add new card
runners:
req: https://trello.com/1
vars:
key: {{APIキー}}
token: {{APIキーで取得したトークン}}
steps:
add:
req:
/cards?key={{ vars.key }}&token={{ vars.token }}&idList={{listのID}}&name=create%20module%20A&desc=create%20module%20A%20for%20serverside:
post:
body:
application/json:
body: ""
test: |
steps.add.res.status == 200 && steps.add.res.body.name == 'create module A'
get:
req:
/cards/{{ steps.add.res.body.id }}?key={{ vars.key }}&token={{ vars.token }}:
get:
body: null
test: |
steps.get.res.status == 200 && steps.get.res.body.name == 'create module A'
まずrunnersでアクセス先を定義します。URL中にAPIバージョンなどあればここに含むことができます。gRPC/DB等アクセス先の判定はURLのプロトコルスキームで判定しているようです。
desc: add new card
runners:
req: https://trello.com/1
varsではシナリオ中で再利用できる変数を定義できます。後述のstepsのなかで {{ vars.key }}
で利用できます。
vars:
key: {{APIキー}}
token: {{APIキーで取得したトークン}}
stepsが実際のAPI実行部分です。下記2行目の add:
はシナリオ1つごとの名前で、わかりやすくつけておくと、レスポンスの値を他のシナリオで利用するときに便利になりそうです。
下記3行目の req は runnners で定義したURLを参照しています。
trelloのAPIはクエリパラメータで各値を設定する形ですが、runnではJSONデータをPOSTすることを念頭に置いているようです。JSONでパラメータを送信する場合は中身をYAMLで記述できます。このAPIではPOSTデータは必要ないので空にしています。
steps:
add:
req:
/cards?key={{ vars.key }}&token={{ vars.token }}&idList={{listのID}}&name=create%20module%20A&desc=create%20module%20A%20for%20serverside:
post:
body:
application/json:
body: ""
test: がAPIレスポンスの検証部分です。前述のとおり組み込み言語の式になっています。レスポンスの結果は steps.[シナリオの名前].res.status
や steps.[シナリオの名前].res.body.name
などで参照できます。
test: |
steps.add.res.status == 200 && steps.add.res.body.name == 'create module A'
前のシナリオの結果を別のシナリオで使い回すことも可能です。
List記法の場合は、steps[1].res.status
のようにテストシナリオのインデックスで値を参照することになります。
req:
/cards/{{ steps.add.res.body.id }}?key={{ vars.key }}&token={{ vars.token }}:
$ runn run new_card.yml
add new card ... ok
1 scenario, 0 skipped, 0 failures
zoncoenさん作でメルカリ社内で使われてるようです。
実は弊社のBiosugar0が既に記事化しておりそちらを見てもらったほうが詳しい(もしかしたら日本一詳しいscenarigoの記事かも)のですが、一応書いていきます。
runnとの差異でいうと以下かなと思います。
$ go install github.com/zoncoen/scenarigo/cmd/scenarigo@v0.12.8
$ scenarigo config init
テストの設定ファイル(senarigo.yaml) シナリオファイル(ディレクトリ)を列挙したりデバッグモードを設定したりできます。
schemaVersion: config/v1
scenarios:
- new_card.yml
テストファイル(new_card.yml)の全体は以下の通りです。APIキーなどは適宜読み替えてください。
title: add new card
vars:
key: {{APIキー}}
token: {{APIキーで取得したトークン}}
steps:
- title: add new card
protocol: http
request:
method: POST
url: "https://trello.com/1/cards?key={{ vars.key }}&token={{ vars.token }}&idList={{listのID}}&name=create%20module%20A&desc=create%20module%20A%20for%20serverside"
expect:
code: OK
body:
name: 'create module A'
bind:
vars:
id: '{{response.id}}'
- title: get added card
protocol: http
request:
method: GET
url: "https://trello.com/1/cards/{{ vars.id }}?key={{ vars.key }}&token={{ vars.token }}"
expect:
code: OK
body:
name: 'create module A'
パーツごとに順番に見ていきます。
runnと同様に変数を定義して、シナリオ中 {{ vars.key }}
で再利用できます。
vars:
key: {{APIキー}}
token: {{APIキーで取得したトークン}}
stepsはシナリオのリストになります。プロトコルやメソッドを宣言的に記述します。
steps:
- title: add new card
protocol: http
request:
method: POST
url: "https://trello.com/1/cards?key={{ vars.key }}&token={{ vars.token }}&idList={{listのID}}&name=create%20module%20A&desc=create%20module%20A%20for%20serverside"
値の検証は、結果として期待される値をYAMLで宣言的に書く形です。
expect:
code: OK
body:
name: 'create module A'
レスポンスの値を再利用したい場合は明示的に再代入します。
bind:
vars:
id: '{{response.id}}'
$ scenarigo run
ok new_card.yml 1.204s
以上、2つのツールの利用方法でした。
runnは動的、scenarigoは静的と、似ているようで違うフィロソフィーがあるなと感じました。
書き方については割と好みかなと思います。
機能的な面で言うと、runnのほうがいつの間にか CDP Runner: Control browser using Chrome DevTools Protocol (CDP) に対応していたり、APIレスポンスをOpenAPI定義と突合できたり、活発に機能追加されている印象です。
scenarigoについてはメルカリ社内でも使われていて比較的枯れているようなので安心感があります。特殊なことをしたい場合でもGo言語でのPlugin機能があるので、最悪そういう逃げ道もありそうです。
まだ導入には至っていませんが、引き続き生産性向上に取り組みたいと思っています。