チェックアウトエンドポイントの堅牢なテストシナリオを構築しました。それは3つのリクエストを連鎖させ、各レスポンスをアサートし、「実行」をクリックするたびに成功します。しかし、パイプラインでは、40通りの入力組み合わせをカバーし、QAリードが管理するファイルからデータを取得し、異なる認証情報でステージング環境と本番環境に対して同じシナリオを実行する必要があるかもしれません。ラップトップで機能したポイントアンドクリックの実行は、そのようには通用せず、シナリオを40回複製したくもないでしょう。
その最後の課題を解決するのがコマンドラインです。Apidogを使用すると、ビジュアルビルダーでテストシナリオを一度作成し、その後apidog-cliパッケージを使ってターミナルからそれを実行できます。一つのシナリオをデータ駆動型実行に変換するフラグは、--iteration-dataの略である-dです。これはCSVファイル、JSONファイル、またはApidogプロジェクトに保存したデータセットを受け取り、行ごとにシナリオを実行し、各行の値をリクエストが参照する変数にバインドします。
-dフラグがファイルを読み取る方法
この機能全体は一つのオプションに集約されています。apidog run --helpから直接引用した、その長形式と短形式を以下に示します。
-d, --iteration-data <path|testDataId> Define the data which use for iterations (either JSON or CSV)
この``が、多くの人が見落とす詳細です。この引数はオーバーロードされています。パスを渡すと、CLIはディスクからローカルファイルを読み込みます。テストデータIDを渡すと、CLIはApidogプロジェクト内に保存されているデータセットを取得します。同じフラグで2つのソースがあり、ランナーはどちらが渡されたかを判別します。
ローカルファイルの形式が一般的な開始点です。コマンドを実行する場所からの相対パスでファイルを指定します。
apidog run --access-token $APIDOG_ACCESS_TOKEN \
-t 605067 -e 1629989 \
-d ./test-data/checkout-cases.csv -r cli
CLIは`checkout-cases.csv`を開き、ヘッダーの下の行数を数え、行ごとにシナリオ`605067`を1回実行します。各パスで、列をリクエスト内の対応する変数にバインドし、シナリオを実行し、そのイテレーションの結果を記録します。40行あれば40回実行され、1つのシナリオが実行されます。
形式はファイルに従います。同じフラグで、追加のオプションなしでJSONを受け入れます。
apidog run --access-token $APIDOG_ACCESS_TOKEN \
-t 605067 -e 1629989 \
-d ./test-data/checkout-cases.json -r cli
CLIにどの形式を使用しているかを伝える必要はありません。拡張子とファイル構造を読み取ります。つまり、列名とJSONキーがシナリオが期待する変数と一致している限り、コマンドを変更することなく、プロジェクトの途中でCSVをJSON配列に交換できます。
CLIが各ファイル内で期待するもの
CSVは、フラットで表形式のケースに使用する形式です。ヘッダー行が変数を命名します。その下の各行が1つのイテレーションです。以下は、割引エンドポイント用の実際の`checkout-cases.csv`の例です。
sku,quantity,coupon,expected_status,expected_total
DESK-01,1,SAVE10,200,89.10
DESK-01,0,SAVE10,422,0
CHAIR-09,3,,200,447.00
DESK-01,1,EXPIRED,410,0
GHOST-99,1,SAVE10,404,0
5つの列が5つの変数になります。リクエストボディ内では`{{sku}}`と`{{quantity}}`を記述し、アサーションでは`{{expected_status}}`と`{{expected_total}}`に対してレスポンスを比較します。ランナーはそれらを各行ごとにバインドします。3行目の空の`coupon`セルは空の文字列になり、これはまさにカバーしたいクーポンなしのケースです。
JSONは、ケースがネストされた構造を持っており、それを列にフラット化するのが難しい場合に使う形式です。ファイルはオブジェクトの配列で、1つのオブジェクトが1つのイテレーションに対応します。
[
{
"label": "valid order, two items",
"order": {
"items": [
{ "sku": "DESK-01", "qty": 1 },
{ "sku": "CHAIR-09", "qty": 2 }
],
"shipping": { "country": "US", "method": "ground" }
},
"expected_status": 200
},
{
"label": "unshippable country",
"order": {
"items": [{ "sku": "DESK-01", "qty": 1 }],
"shipping": { "country": "ZZ", "method": "ground" }
},
"expected_status": 422
}
]
シナリオ内では、同様に`{{order}}`と`{{expected_status}}`を参照し、ランナーは各オブジェクトのフィールドをイテレーションに渡します。`label`フィールドはあなた向けです。レポートに表示されるため、失敗した実行が「イテレーション2」ではなく「配送不能な国」と読めるようになり、これは5秒の診断と5分の診断の違いをもたらします。
これらのファイルがCIで問題を引き起こさないようにするためのいくつかのルールを以下に示します。
- ヘッダー名とシナリオ内の変数名を一致させてください。`qty`という列は、`{{quantity}}`を読み取るリクエストにはバインドされません。この不一致が、データ駆動型実行がローカルでは成功するのに、パイプラインで空の値を生成する最も一般的な理由です。
- カンマを含むCSVフィールドは引用符で囲むか、そのファイルをJSONに変換してください。カンマを含む自由記述フィールドは2つの列に分割され、その後のすべての値がずれてしまいます。
- 期待される結果はシナリオではなく、データ内に記述してください。1行目が200を期待し、4行目が410を期待するといった具合です。アサーションで期待値をハードコーディングすると、再びケースごとに1つのシナリオに戻ってしまいます。
- これらのファイルはテスト設定の隣にコミットし、テスト対象のコードと共にバージョン管理されるようにしてください。データファイルはテストの一部であり、単なるばらばらのアーティファクトではありません。
これらのバインドされた値を読み取るアサーションの記述におけるJSONPathについては、JSONレスポンスからのアサーション設定と変数の抽出で構文の詳細が説明されています。
ファイルではなく保存されたデータセットから実行する
`-d`の2番目の形式は、ほとんどのウォークスルーでは紹介されていません。パスの代わりに、テストデータIDを渡します。
apidog run --access-token $APIDOG_ACCESS_TOKEN \
-t 605067 -e 1629989 \
-d 38291 -r cli
これでCLIは、ランナーのディスクからファイルを読み込むのではなく、そのIDを持つデータセットをApidogプロジェクトから取得します。これは、データがリポジトリではなくチームと共に存在する場合に役立ちます。QAリードはApidog内でケーステーブルを管理し、アプリで編集すると、誰もCSVをコミットすることなく、すべてのCI実行で最新バージョンが取得されます。シナリオ、環境、データはすべてサーバー側にあり、コマンドはそれらをIDで指定するだけです。
トレードオフは、信頼できる情報源がどこにあるかです。コミットされたCSVは、プルリクエストで差分を確認でき、テスト対象のコミットに紐付けられたデータセットを提供します。保存されたテストデータIDは、全員が1か所で編集する単一の共有テーブルを提供します。どちらも間違いではありません。データがコードと同期して動くべき場合はコミットされたファイルを、データがリポジトリに触れない人々によって管理される場合は保存されたIDを選択してください。
エクスポートされたファイルからオフラインで実行する
CLIにデータを供給する方法は3つ目があり、それはコマンド全体の形式を変えます。Apidogからテストケースを自己完結型ファイルとしてエクスポートし、そのファイルを直接実行できます。これにより、シナリオIDもシナリオを取得するためのネットワークラウンドトリップも不要になります。
apidog run ./checkout.apidog-cli.json -r cli,html
ここで最初の引数は`file-source`であり、フラグではなくエクスポートされたテストケースそのものです。CLIはファイル内のものを実行します。その上から、` -d`でイテレーションデータを重ねます。
apidog run ./checkout.apidog-cli.json -d ./checkout-cases.csv -r cli,junit
これは2つの状況で重要です。1つ目は、シナリオIDを解決するためにApidogクラウドにアクセスできない、エアギャップ環境やロックダウンされたCIランナーの場合です。エクスポートされたファイルには、実行に必要なすべてが含まれています。2つ目は再現性です。エクスポートされたファイルは、エクスポート時のシナリオのフリーズされたスナップショットであるため、後で誰かがアプリでシナリオを編集しても、そのファイルからの実行には影響しません。このインストールと初回実行のメカニズムについては、Apidog CLIインストールガイドでバイナリの配置方法が説明されており、Apidog CLI完全リファレンスではすべてのフラグが1つの表にまとめられています。
-dと`-n`、変数オーバーライドの組み合わせ
データ駆動型実行が単独で行われることは稀です。常に3つのフラグが`-d`と組み合わされます。
`-n, --iteration-count`は、シナリオが何回実行されるかを設定します。データファイルを指定した場合、行数がすでにイテレーションを駆動するため、通常は`-n`を省略し、ファイルに決定を任せます。`-n`を使用するのは、主にテーブルを複数回実行したい場合や、固定されたシナリオを繰り返すソークテストのように、データファイルを全く使用せずに実行する場合です。
apidog run --access-token $APIDOG_ACCESS_TOKEN -t 605067 -e 1629989 -n 50 -r cli
`--env-var`と`--global-var`は、プロジェクト内の環境に触れることなく、実行時に`key=value`ペアを挿入します。これにより、機密情報やパイプラインごとの設定をシナリオとデータファイルの両方から除外できます。データファイルはテストケースを保持し、オーバーライドは実行ごとに変化するものを保持します。
apidog run --access-token $APIDOG_ACCESS_TOKEN \
-t 605067 -e 1629989 \
-d ./test-data/checkout-cases.csv \
--env-var "base_url=https://staging.internal" \
--global-var "api_key=$RUNTIME_API_KEY" \
-r cli,junit
この分離は意図的なものです。イテレーションデータは、どこでも同じ実行部分、つまりエンドポイントが処理しなければならないケースです。変数オーバーライドは、環境ごとに変化する部分、つまりホスト、キー、テナントです。資格情報はCIのシークレットストアに保管し、上記の`$RUNTIME_API_KEY`のように、環境変数から`--global-var`を通じて渡してください。リポジトリへのアクセス権を持つ誰もが読み取れるCSVにそれらを焼き付けることは絶対に避けてください。
イテレーションごとの結果の読み取り
データ駆動型実行は、どの行が失敗したかを識別できる場合にのみ有用です。レポーターフラグが、得られる結果を決定します。
apidog run --access-token $APIDOG_ACCESS_TOKEN \
-t 605067 -e 1629989 \
-d ./checkout-cases.csv \
-r cli,junit --out-dir ./test-reports
`-r cli`は、読みやすいイテレーションごとの内訳をターミナルに出力します。これはビルドログで確認するものです。`-r junit`はJUnit XMLを書き出します。これはほとんどのCIダッシュボードがパス/フェイルツリーに解析する形式であり、失敗した行がログテキストに埋もれるのではなく、名前付きの失敗テストとして表示されます。`html`と`json`も渡すことができます。`html`はビルドアーティファクトとしてアーカイブできるブラウザで閲覧可能なレポートを提供し、`json`は結果を後処理する場合に生の構造化された出力を提供します。`--out-dir`はファイルが保存される場所を制御し、それらをアーティファクトとして保持できるようにします。
デフォルトでは、実行は最初のアサーション失敗で停止します。広範なデータテーブルの場合、通常これは適切ではありません。なぜなら、一度の実行ですべての失敗した行を確認したいのであって、一つ修正して次の失敗を見つけるために再実行したくないからです。`--on-error`でこの動作を切り替えます。
apidog run --access-token $APIDOG_ACCESS_TOKEN \
-t 605067 -e 1629989 \
-d ./checkout-cases.csv \
--on-error continue \
-r cli,junit --out-dir ./test-reports
`--on-error continue`は、以前のイテレーションが失敗してもすべてのイテレーションを実行するため、単一のレポートで2行目、7行目、19行目が一度に壊れていることを示します。何かが失敗した場合でも実行は非ゼロの終了コードで終了するため、それは実際のゲートとしての役割を果たします。`--on-error end`は、迅速なスモークチェックのための高速失敗のデフォルトです。`ignore`は、実行を脱線させたくない、まれに既知の不安定なステップ用です。
何もせず静かに失敗するバインディングのデバッグ
データ駆動型実行で最も時間を浪費する失敗モードは、アサーションの失敗ではありません。それは、データがリクエストにバインドされなかったために、何もテストせずに成功してしまった「グリーンラン」です。リクエストは空の値で実行され、エンドポイントは空のペイロードに対して200を返し、アサーションはたまたま成功しました。カバレッジは問題ないように見えますが、実際にはそうではありません。
データ駆動型実行が奇妙な動作をする場合は、`--verbose`を追加します。
apidog run --access-token $APIDOG_ACCESS_TOKEN \
-t 605067 -e 1629989 \
-d ./checkout-cases.csv \
--verbose -r cli
`--verbose`は、各イテレーションの完全なリクエストとレスポンスを出力します。ランナーが実際に送信したリクエストボディを確認してください。`{{sku}}`が置換されずにそのまま残っている場合や、CSVセルが空でなかったにもかかわらず値が空白になっている場合は、バインディングが壊れています。よくある3つの原因を、発生頻度順に示します。
- 列名と変数名が一致していない。CSVヘッダーが`product_sku`で、リクエストが`{{sku}}`を読み取っている場合など。どちらか一方の名前を変更して一致させてください。
- 余分なカンマがCSVの行を分割してしまった。引用符で囲まれていない自由記述フィールドが1つあったために、その後のすべての列がずれ、`expected_status`が本来次の列にあるべき値を持つようになったなど。そのフィールドを引用符で囲むか、ファイルをJSONに切り替えてください。
- CIの作業ディレクトリからのデータファイルへのパスが間違っている。ラップトップでは正しく解決されるのに、パイプラインでは何も読み込まれないというサイレントな失敗。チェックアウトステップが生成するリポジトリルートからの相対パスを使用し、実行前のステップでファイルが存在することを確認してください。
一般的なルールとして、イテレーションが成功してもそれが本来あるべきではないと疑われる場合は、その成功を信用する前に、冗長モードでのリクエストを一度読んでください。空の入力に対して実行されるテストは、テストがないよりも悪い状況です。なぜなら、エンドポイントがテストされていないにもかかわらず、すべてがうまくいっていると伝えてしまうからです。
CIへの組み込み
その成果は、誰かが「実行」をクリックすることなく、変更があるたびにテーブル全体が実行されることです。以下は、CLIをインストールし、プルリクエストごとにCSV駆動型シナリオを実行するGitHub Actionsジョブです。
name: Data-driven API tests
on: [pull_request]
jobs:
api-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Apidog CLI
run: npm install -g apidog-cli
- name: Run data-driven scenario
env:
APIDOG_ACCESS_TOKEN: ${{ secrets.APIDOG_ACCESS_TOKEN }}
run: |
apidog run --access-token $APIDOG_ACCESS_TOKEN \
-t 605067 -e 1629989 \
-d ./test-data/checkout-cases.csv \
--on-error continue \
-r cli,junit --out-dir ./test-reports
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: api-test-report
path: ./test-reports
トークンは、リポジトリ設定で一度設定された`secrets.APIDOG_ACCESS_TOKEN`から取得されます。`--on-error continue`は、最初の失敗で停止する代わりに、すべての失敗した行を1つのレポートに収集します。アップロードの`if: always()`は、実行が失敗した場合でもレポートを保持します。失敗時こそレポートを最も読みたい時だからです。CSVパスをJSONファイルまたは保存されたテストデータIDに置き換えても、他には何も変更ありません。
同じ3つの手順で、任意のCIシステムに移植できます。Node.jsとCLIをインストールし、トークンを環境変数として公開し、`-d`を付けて`apidog run`を呼び出します。GitLab CI、Jenkins、CircleCIなどでは、プラットフォームごとにテストを書き直す必要はありません。Actions側のより詳細な手順については、GitHub ActionsでのAPIテスト自動化を参照してください。CLIのレポーター、エラー処理、TLSに関するすべてのフラグについては、Apidog CLI完全ガイドにすべてのオプションが記載されています。
テストを肥大化させずにスケーリングするワークフロー
1つのシナリオと3つの行から始めましょう。アプリ内で、リクエストに変数を参照し、アサーションに期待される結果を記述してシナリオを構築します。ハッピーパス、既知の失敗、および1つの境界ケースを含むCSVを作成します。3つのイテレーションすべてが正しく動作するまで、`-d`を使用してローカルで実行します。
次に、シナリオではなくデータを増やしてください。すべてのバグ報告は、正しい期待出力を持つ新しい行になります。そのバグは永続的な回帰テストケースに変わり、あなたは新しいテストを書くのではなく、ファイルに行を追加しただけです。数ヶ月後には、そのファイルには本番環境でエンドポイントが直面する実際の課題が蓄積されていくでしょう。
最後に、`--on-error continue`と`junit`レポーターを付けて、`apidog run -d`コマンドをCIに組み込みます。これで、期限切れクーポン行を壊す変更がプッシュされた瞬間、ビルドは失敗し、壊れたケースを直接指し示す名前付きイテレーションが表示されます。テーブルがどれほど広くなっても、シナリオは小さく読みやすいままです。これが複合的な勝利です。データの入力によってカバレッジが拡大し、メンテナンスは横ばいのままです。
このようなCLIランナーがコードファーストフレームワークと比較して、ご自身のスタックに適しているかどうかまだ決めかねている場合は、CSVまたはJSONを使用したデータ駆動型APIテストにどのツールを選択するかの分析でアプローチが比較されており、Apidog CLI vs NewmanではPostmanの世界から最も近いコマンドラインの類似品が取り上げられています。
よくある質問
`-d`はファイルパスと保存されたデータセットの両方を受け入れられますか? `-d`フラグは、実行ごとにどちらか一方を受け入れます。つまり、ローカルのCSVまたはJSONパス、あるいはApidogプロジェクトに保存されたデータセットを指すテストデータIDです。値を1つ渡します。データがリポジトリと共にバージョン管理されるべき場合はファイルパスを使用し、共有テーブルがアプリ内にありコピーをコミットしたくない場合は保存されたIDを使用します。
ファイルがCSVかJSONかをCLIに伝える必要がありますか? いいえ。ランナーはファイル自体からフォーマットを読み取るため、同じ`-d`フラグで両方を処理します。列名(CSV)またはオブジェクトキー(JSON)をシナリオが参照する変数名と一致させておけば、コマンドを変更せずにフォーマットを切り替えることができます。
`-d`と`-n`を一緒に使用するとどうなりますか? データファイルの行数がイテレーション数を決定するため、`-d`を使用する場合は通常`-n`は不要です。ソークテストのようにデータファイルなしで実行を繰り返したい場合や、テーブル全体を複数回実行したい場合に`-n`を使用します。
データ駆動型実行が何もテストせずに成功したのはなぜですか? 最も一般的な原因は、バインディングが実行されなかったことです。変数名と一致しない列名や、何も読み込まなかったCIでの誤ったファイルパスなどが考えられます。`--verbose`を付けて一度実行し、CLIが送信したリクエストボディを確認してください。未置換の`{{variables}}`や空白の値が見つかった場合は、その緑色の結果を信用する前に、名前の不一致やパスを修正してください。
データファイルから認証情報を除外するにはどうすればよいですか? トークンやキーはCSVまたはJSONから完全に除外してください。それらは、CIのシークレットストアから`--global-var`または`--env-var`を使用して実行時に渡します。例えば、`--global-var "api_key=$RUNTIME_API_KEY"`のようにです。データファイルにはテスト入力と期待される結果のみを保持し、実行を認証するものは何も含めるべきではありません。
同じデータをステージング環境と本番環境に対して実行できますか? はい、できます。シナリオとデータファイルは固定したまま、`-e`でターゲットを切り替えます。プルリクエストチェックをステージング環境IDに向け、デプロイ後のスモークテストを本番環境に向ける際に、同じシナリオとデータを使用し、`-e`の値だけを変えることができます。環境とデータを分離することが、この機能が機能する理由のすべてです。
まとめ
`-d`フラグは、Apidog CLIにおけるデータ駆動型テストの全貌であり、一見した以上に柔軟です。ローカルのCSVファイルやJSONファイルを読み込んだり、プロジェクトにIDで保存されたデータセットを読み込んだりできます。繰り返し実行のための`-n`、実行ごとの設定のための`--env-var`と`--global-var`、そしてすべての失敗行を一度の実行で表示するための`--on-error continue`と組み合わせて使用します。オンラインでシナリオIDから実行することも、エクスポートされたファイルからオフラインで実行することもでき、`junit`および`cli`レポーターを通じてイテレーションごとの結果を読み取ることができます。
シナリオを一度構築し、各ケースを行として記述し、誰かがファイルに行を追加するたびにランナーがカバレッジを広げるようにしましょう。Apidogをダウンロードし、`apidog run`を最初のデータファイルに向けて、単一のシナリオをすべてのプッシュで実行されるケースのテーブルに変えましょう。
