Scenarigoを用いたAPIテストの取り組み

はじめに

KyashのSET(Software Engineer in Test)チームでは、Scenarigoを用いてAPIテスト自動化の取り組みを行なっています。
非常にシンプルにテストを実装できるため、Scenarigo自体の導入やキャッチアップのハードルはそれほど高くないと思います。
今回はKyashでAPIテストを運用していくにあたって、工夫している点を紹介したいと思います。

ざっくり構成の話

|--.github
|  |--workflows # CIで実行するワークフローを配置
|--scenarigo
|  |--environments # 実行時に必要な環境変数をまとめたファイルを入れておくディレクトリ
|  |  |--dev.env
|  |  |--staging.env
|  |--plugins # UUIDの生成といった拡張処理を行うGoプラグインを入れるディレクトリ
|  |  |--uuid.go
|  |  |--uuid.so
|  |--configs # scenarigoの設定ファイルを入れるディレクトリ
|  |  |--scenarigo-dev.yaml
|  |  |--scenarigo-staging.yaml
|  |--scenarios # シナリオのファイル群を入れるディレクトリ
|  |  |--dev
|  |  |  |--login
|  |  |  |  |--200_メールアドレス・パスワードでログイン成功.yml
|  |  |  |  |--500_メールアドレス・パスワードでログイン失敗.yml
|--scripts # 実行スクリプトを配置
 |  |--scenarigo_run.sh

テストシナリオは、scenarios配下にさらに機能(API)ごとのディレクトリを作成し、そこに配置しています。ディレクトリ名はエンドポイントのURLをなぞったものにし、ファイル名は {ステータスコード}_{日本語のケース名}.yml としています。 .github/workflowsには、pull requestの作成をトリガーとしてScenarigoのテストを実行するワークフローを配置しています。正確にはscriptsに配置された「Scenarigoのテスト実行スクリプト」を実行するワークフローです。スクリプトでは scenarigo run コマンドによるテスト実行だけでなく、以下のようにenvファイルの読み込みやpluginのビルドといった事前準備、テスト実行後の環境変数の削除などを行なっています。

#!/bin/bash

set -e

# テストを実行する環境(デフォルトはdev)
env='dev'

while getopts e: OPT
do
  case $OPT in
     e) env=$OPTARG;;
  esac
done

cd "$(dirname "$0")/../scenarigo"

# テストの実行に必要な環境変数をセット
set -a
source environments/$env.env
set +a

# scenarigoで使うpluginのビルド(pluginのビルドとscenarigo実行のGoバージョンは同じでなければいけないため)
for f in `find plugins/**.go -maxdepth 1 -type f`; do
    go build -buildmode=plugin -o ${f%.*}.so $f
done

echo "Scenarigo run on $env env"
scenarigo run --config configs/scenarigo-$env.yaml

# SCNARIGO_ prefixのつく環境変数を削除
unset ${!SCENARIGO_@}

1. パターンを網羅するのは諦めて優先度を決める

1つのAPIでもアカウントのステータスや決済履歴の有無などの条件によりレスポンスの内容のパターンが複数存在する場合があります。様々なパターンのテストを実装しておきたくなりますが、全パターンを網羅したテストデータを用意するだけで相当な手間と時間がかかってしまうため、準備が困難かつ重要度の低い条件のテストは一旦実装しない方針としました。重要度は、動作しなかった場合のインパクトやAPIの呼び出し回数、バックエンドエンジニアへのヒアリングなどをもとに決定しました。

2. 適度に共通化する

Scenarigoでは include という仕組みを使うことで、テストシナリオ内で別のyamlファイルに定義したシナリオを実行し、変数を引き継ぐこともできます。 KyashではAPIの呼び出しに、ログイン時に発行されるトークンが必要となるケースが多いため、ログイン処理を共通化しています。具体的には、ログインのAPIを呼び出すシナリオを実装し、各テストシナリオ内でまず最初に呼び出し、ログインのAPIのレスポンスのtokenを、テストしたいAPIのリクエストパラメータに設定します。

steps:
- title: ログインする
  vars:
    email: '{{env.xxx}}'
    password: '{{env.xxx}}'
  include: 'login.yml' # ログインのシナリオを呼び出し
  bind:
    vars:
      accessToken: '{{vars.accessToken}}' # ログインのAPIのレスポンスのtoken

- title: ユーザー情報を取得する
  protocol: http
  request:
    method: GET
    url: '{{env.xxx}}/v1/xxx
    header:
      content-type: application/json; charset=utf-8
      token: '{{vars.accessToken}}' 

このとき共通化したのはあくまでログインの処理です。ログインのAPIのリクエスト情報(メールアドレスやパスワードといったアカウントの情報)はenvファイルに環境変数として定義したものを、各テストシナリオで変数にセットし、ログインのシナリオで使用できる形としています。そのため、login.yml のシナリオを単体で実行すると、呼び出し元のテストシナリオから渡ってくるはずの情報が不足しているため必ず失敗します。そこで当初想定していた運用を少し見直す必要が出てきました。

実行対象のシナリオの指定方法を変更

Scenarigoではscenarigo.yamlという設定ファイルの情報を元にテストが実行されます。scenarigo.yamlでは実行するテストシナリオのファイル名やディレクトリ名を指定できます。
テストシナリオはすべて、scenarios配下に作成した機能(API)ごとのディレクトリに追加していくことをルールとしているため、実行対象として各テストシナリオ個別のファイル名ではなくscenariosフォルダを丸ごと指定していました。

schemaVersion: config/v1

scenarios:
- scenarios
…

しかし、この指定方法ではscenarios配下のシナリオはすべて単独で実行されてしまうため、先述のログインのように他のシナリオから呼び出されることを想定しているシナリオや、別のAPIとセットで呼ぶ必要があるAPIのテストシナリオについて不都合が生じました。
例えば、「入力値の有効性チェックを行いパスしたらトークンを発行し、そのトークンを用いて情報を登録する」といった場合、トークンがないと失敗してしまう登録のAPIのテストシナリオは、単体ではなく有効性チェックのAPIテストとセットで実行する必要があります。意図しないタイミングでシナリオが実行されることを避けるため、実行したいテストシナリオあるいはそのシナリオが含まれるディレクトリをscenarigo.yamlで明確に指定する形に変更しました。 登録のテストシナリオ内で有効性チェックのテストシナリオをincludeし、実行対象のファイルとして登録のテストシナリオのみを指定します。

schemaVersion: config/v1

scenarios:
- scenarios/login
- scenarios/setting
… # 新しいAPIのテストを追加したらここにも追記する

3. テストシナリオや設定ファイルを環境ごとに用意する

環境変数を定義するenvファイルに加え、先述の設定ファイル(scenarigo.yaml)やテストシナリオも専用のものを用意し、実行するシナリオを環境ごとに変更できるようにしました。
scenarigo run コマンドには--configというオプションがあり、設定ファイルのパスを指定することができます。環境ごとに設定ファイルを用意しておくことで、「development環境でテストを実行したい場合にはconfigs/scenarigo-dev.yamlを参照する」といった指定が可能です。

echo "Scenarigo run on $env env"
scenarigo run --config configs/scenarigo-$env.yaml # $envはスクリプト実行時の引数として渡す

なぜテストシナリオを使い回さず、実行する環境によって分ける必要があるかと言うと、テストの期待値に、ステータスコードだけでなくレスポンスの詳細な内容も含めているためです。
development環境に存在するアカウントを対象に実装したテストは、まったく同じ条件(残高、決済額、履歴の日時や申請のステータスなど)を持つアカウントを用意できない他の環境では実行できないため、テストシナリオ自体を分ける運用としました。

まとめ

テストの実装方法にも運用にもまだまだ改善の余地はあると思います。
最初からあらゆる問題を想定し回避策を用意できていることが理想かもしれませんが、実際にテストを拡充し運用に乗せていく過程で見えてきた問題に対処していくことで得たものは非常に大きいです。 今後もKyahのバリュー「動いて風を知る」の精神で、走りながら最善の道を模索し、より開発に寄与できる自動テストの仕組み作りを行なっていきたいと思います。