ログインテストを作成しました。テストは成功します。しかし、チームメイトは当然の疑問を投げかけます。ロックされたアカウント、未確認のメールアドレス、末尾にスペースがあるパスワード、誰かが最終的にフィールドに貼り付けるであろうSQLインジェクション文字列に対しても成功するのか、と。ここで選択を迫られます。そのテストを5回コピーして各コピーで1つの値を変更するか、同じテストに多数の入力行を与えて全て実行させる方法を見つけるかです。
コピペによる方法は、ほとんどのテストスイートが腐敗する原因となります。ほぼ同一の5つのテストが1年をかけてばらばらになります。1つは新しいアサーションが追加され、他には追加されません。フィールド名が変更されると、そのうちの4つがサイレントに壊れます。最終的には、1つであるべき5つのものを保守することになります。パラメーター化テストは、これを根本から解決します。テストを一度書き、それに入力と期待される出力の表をポイントするだけです。1つのシナリオで何百ものケースに対応し、編集する場所は1つです。
パラメーター化テストが実際に意味するもの
パラメーター化テスト(データ駆動型テストとも呼ばれる)は、テストのロジックと、そのロジックを実行するデータを分離します。ロジックとは、リクエストを送信し、ステータスコードを確認し、レスポンスのフィールドを検証するといった一連のステップのことです。データとは、そのロジックで実行したい入力と期待値のセットです。
割引コードのエンドポイントに対する単一のテストシナリオを想像してみてください。ロジックは常に同じです。コードを含む`POST /api/orders`を送信し、レスポンスをアサートします。ケースごとにデータが異なります。
| コード | 注文合計 | 期待されるステータス | 期待される割引額 |
|---|---|---|---|
| WELCOME10 | 100 | 200 | 10 |
| WELCOME10 | 5 | 422 | 0 |
| EXPIRED | 100 | 410 | 0 |
| (空) | 100 | 400 | 0 |
| FAKE123 | 100 | 404 | 0 |
5行、5つの異なる動作、1つのテスト。ランナーは行を繰り返し処理します。各パスで列の値を変数にバインドし、リクエストを発行し、その行の期待値に対してアサーションをチェックします。行3が、期限切れコードが410ではなく200を返したために失敗した場合、1つの明確な失敗が1つの行を指し示します。どのコピーが壊れたかを特定するために、5つの異なるテストファイルを捜し回る必要はありません。
このパターンは、境界で最も重要になります。ハッピーパスの網羅は簡単に書けますが、午前2時にあなたを呼び出すバグを捕らえることはめったにありません。バグは境界ケースに存在します。空文字列、負の数、Unicode名、期限切れのトークン、制限を1セント超える値などです。パラメーター化により、境界ケースの追加がスプレッドシートに1行追加するのと同じくらい簡単になります。
別のデータファイルがハードコードされた値よりも優れている理由
各ケースをテスト内に直接ハードコードすることもできます。ほとんどの人がそこから始めます。問題は後から現れます。
データがテスト内に存在する場合、非エンジニアはケースを追加できません。QAリーダーは、このエンドポイントを以前に壊した15の厄介な入力を知っていますが、コードを編集してプルリクエストを開かなければ、それらを追加することはできません。データがCSVに存在するなら、彼らはスプレッドシートを編集してコミットするだけです。障壁はほぼゼロになります。
別のファイルを使用することで、テストシナリオの可読性も維持されます。外部ファイルをループするテストは短く、1つのリクエスト、いくつかのアサーションで完了します。30のインラインケースがあるテストは、誰も触りたくない繰り返しだらけの壁です。そして、プログラムでケースを生成する必要がある場合、例えば本番ログから1000行を引っ張ってくる場合、ファイルが唯一まともな選択肢です。1000個のケースをテスト本体に貼り付けることはできません。
選択するフォーマットは、データの形状によって異なります。フラットな表形式のケースにはCSVが適しています。ネストされた構造化ペイロードにはJSONが適しています。Apidogのランナーではどちらも第一級の入力であるため、選択はツールによる制限ではなく、データに関するものです。
データファイルの設定
表形式のケースにはCSVから始めましょう。ヘッダー行は変数の名前を示し、それ以下の各行が1つのイテレーションです。以下は、実際のファイル `discount-cases.csv` としての割引コードの表です。
code,order_total,expected_status,expected_discount
WELCOME10,100,200,10
WELCOME10,5,422,0
EXPIRED,100,410,0
,100,400,0
FAKE123,100,404,0
各列ヘッダーは、テスト内で参照する変数になります。リクエストボディには`{{code}}`と`{{order_total}}`を記述し、アサーションでは`{{expected_status}}`と`{{expected_discount}}`と比較します。ランナーは行ごとにバインディングを行います。
入力がネストされている場合は、JSONを使用します。反復ごとに1つのオブジェクトを持つオブジェクトの配列は、列にフラット化するのが難しい構造化データを各ケースが持つことを可能にします。以下は、ペイロードにネストされたフィールドがあるユーザー作成エンドポイント用の`user-cases.json`です。
[
{
"scenario": "valid full profile",
"user": {
"email": "ada@example.com",
"roles": ["admin", "billing"],
"profile": { "country": "US", "timezone": "America/New_York" }
},
"expected_status": 201
},
{
"scenario": "missing email",
"user": {
"email": "",
"roles": ["viewer"],
"profile": { "country": "GB", "timezone": "Europe/London" }
},
"expected_status": 400
},
{
"scenario": "unknown role",
"user": {
"email": "grace@example.com",
"roles": ["wizard"],
"profile": { "country": "CA", "timezone": "America/Toronto" }
},
"expected_status": 422
}
]
テスト内では、`{{user}}`、`{{expected_status}}`という同じ構文で構造化された値を参照し、Apidogは各オブジェクトのフィールドをイテレーションに渡します。`scenario`列はあなた自身のためのラベルであり、失敗したイテレーションが「イテレーション3」ではなく「unknown role」とレポートに表示されます。
データファイルが問題を引き起こさないようにするためのいくつかのルール:
- 1つの懸念事項を1つのファイルにまとめる。割引コードファイルとユーザー作成ファイルは、混合列を持つ1つのファイルではなく、2つのファイルです。
- テストではなく、データに期待される結果を含める。ポイントは、行2が422を期待し、行1が200を期待することです。期待値がハードコードされている場合、ケースごとに1つのテストに戻ってしまいます。
- CSVでカンマを含むものは引用符で囲むか、そのファイルをJSONに切り替える。カンマを含む自由記述フィールドは、古典的なCSVの落とし穴です。
- これらのファイルを、テスト対象のコードと一緒にバージョン管理されるように、リポジトリ内の他のテスト設定ファイルの隣に保存する。
Apidogでのパラメーター化シナリオの構築
Apidogアプリで、他のシナリオと同じようにテストシナリオを一度構築します。エンドポイントへのリクエストを追加します。ボディでは、リテラル値を変数参照(`{{code}}`、`{{order_total}}`など)に置き換えます。これらはデータファイルの列です。
次に、同じファイルから読み取るアサーションを追加します。割引の例では、レスポンスステータスが`{{expected_status}}`と等しく、JSONボディ内の割引フィールドが`{{expected_discount}}`と等しいことをアサートします。入力と期待される出力の両方が行から来るため、同じアサーションロジックがすべてのケースを正しく検証します。Apidogでアサーションを記述したことがない場合は、APIアサーション:実践ガイドでパターンが説明されており、JSONレスポンスからアサーションを設定し変数を抽出する方法でJSONPathの詳細が示されています。
データを接続するには、テストシナリオの実行設定を開き、CSVまたはJSONファイルをイテレーションデータソースとしてアタッチします。Apidogはファイルを読み込み、行数をカウントし、行ごとにシナリオを一度実行して、各行の列を対応する変数にバインドします。アプリ内で実行すると、イテレーションごとの内訳が表示されます。どの行が成功し、どの行が失敗したか、そして失敗した各アサーションの実際値と期待値です。
パラメーター化がスイートの他の部分と組み合わせられるのもここです。パラメーター化されたシナリオは依然としてシナリオなので、いくつかのシナリオをテストスイートにグループ化し、全体のセットを1つのジョブとして実行できます。データ駆動型ループは単一のエンドポイント内の広さを処理し、テストスイートはエンドポイント間のカバレッジを処理します。
コマンドラインからの実行
アプリは構築とデバッグを行う場所です。CIはテストがその役割を果たす場所であり、誰かがボタンをクリックすることなくすべてのプルリクエストで実行されます。その引き渡しのためにApidog CLIがあります。これは、アプリで構築したシナリオを受け取り、同じイテレーションデータでターミナルからヘッドレスで実行します。
CLIはnpmパッケージとして配布されています。グローバルにインストールします。
npm install -g apidog-cli
バイナリは`apidog`なので、すべてのコマンドは`apidog run`で始まります。基本的な実行では、IDでシナリオを、IDで環境をポイントします。
apidog run --access-token $APIDOG_ACCESS_TOKEN -t 605067 -e 1629989 -r cli
そのシナリオをデータファイルから駆動するには、イテレーションデータフラグを追加します。これはJSONまたはCSVファイルへのパスを受け入れます。
apidog run --access-token $APIDOG_ACCESS_TOKEN -t 605067 -e 1629989 \
-d ./discount-cases.csv -r cli,junit --out-dir ./test-reports
`-d`フラグ(ロングフォーム`--iteration-data`)は、コマンドラインからのパラメーター化実行の核心です。Apidogはファイルを読み込み、行ごとにシナリオを一度実行し、各イテレーションをレポートします。`discount-cases.csv`を`user-cases.json`に置き換えても同じフラグでJSON配列を処理します。ランナーはファイルからフォーマットを認識します。アクセストークンはパスワードのように扱い、CIのシークレットとして保存し、コミットされるファイルには絶対に含めないでください。そのため、すべての例ではリテラル値ではなく`$APIDOG_ACCESS_TOKEN`を参照しています。
いくつかのフラグは、パラメーター化された実行と自然に組み合わされます。
- ` -d, --iteration-data` は、実行をCSVまたはJSONファイルにポイントします。これは、シングルパス実行をデータ駆動型に変えるものです。
- ` -n, --iteration-count` は、シナリオを固定回数実行します。データファイルを提供すると、通常は行数がイテレーションを駆動するため、` -n ` は主にロードテストのようなデータなしの繰り返しケースで使用します。
- ` -r, --reporters` は出力形式を選択します。可読性のあるビルドログ出力には ` cli ` を、CIダッシュボードがイテレーションごとの合否ツリーとして解析するXMLを出力するには ` junit ` を使用します。
- ` --out-dir` はレポートの保存先を設定し、ビルド成果物としてアーカイブできるようにします。
インストールされているバージョンのフラグの正確な最新リストを知りたい場合は、`apidog run --help`を実行してください。CLIは、すべてのオプションをショートフォームとロングフォームで表示します。
パラメーター化された実行をCIに組み込む
パラメーター化されたテストに投資する理由は、自動的に効果が得られるからです。以下は、CLIをインストールし、すべてのプルリクエストでCSV駆動型シナリオを実行するGitHub Actionsジョブの例です。
name: 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 parameterized API tests
env:
APIDOG_ACCESS_TOKEN: ${{ secrets.APIDOG_ACCESS_TOKEN }}
run: |
apidog run --access-token $APIDOG_ACCESS_TOKEN \
-t 605067 -e 1629989 \
-d ./tests/discount-cases.csv \
-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`から取得され、リポジトリ設定で一度設定されます。`junit`レポーターはXMLを書き込み、ほとんどのCIダッシュボードはこれをイテレーションごとの結果ツリーに変換するため、失敗した行はログテキストの壁ではなく、名前付きの失敗テストとして表示されます。アップロードステップの`if: always()`は、実行が失敗した場合でもレポートを保持できることを意味し、まさにその時にレポートが必要となります。Actions側のより詳細な説明については、GitHub ActionsでAPIテストを自動化する方法を参照してください。
同じシナリオと同じデータファイルは、どのCIシステムでも実行されます。GitLab CI、Jenkins、CircleCIなど、すべて同じ3つの手順に還元されます。NodeとCLIをインストールし、トークンを環境変数として公開し、`-d`フラグを指定して`apidog run`を呼び出すだけです。プラットフォームごとにテストを書き直す必要はありません。
パラメーター化テストアプローチの比較
Apidogだけがデータ駆動型APIテストを実行する唯一の方法ではありません。適切なものを選ぶために、その状況を知っておく価値があります。
Postmanとそのランナーもデータファイルをサポートしています。PostmanのCollection RunnerまたはNewmanコマンドラインツールを使用すると、CSVまたはJSONファイルを添付し、リクエストで`{{column}}`変数を参照します。これはここのパターンとよく似ています。これは有能なアプローチであり、十分に文書化されています。トレードオフは、テストロジックがJavaScriptの事前リクエストスクリプトとテストスクリプトに存在するため、アサーションが増えるにつれて、より多くのコードを保守することになる点です。特にコマンドラインランナーを検討している場合は、Postman CLI vs Newmanで違いが詳細に説明されています。
`@pytest.mark.parametrize`を使ったpytest、JUnitの`@ParameterizedTest`、またはREST Assuredのようなコード優先のフレームワークは、完全なプログラミング言語制御を提供します。これらは、テストロジックが本当にコードを必要とする場合、例えば複雑なセットアップ、カスタムデータ生成、既存のテストコードベースとの密接な結合などに適しています。コストは、すべてのケースがコードに存在するため、非エンジニアは貢献できず、HTTPの配管を自分で維持する必要があることです。
Apidogの視点は、シナリオは視覚的でデータは外部にあるため、ロジックは読みやすく、ケースはスプレッドシートを編集できる人なら誰でもアクセス可能でありながら、同じシナリオがCIでヘッドレスに実行されるというものです。CSVおよびJSONのデータ駆動型実行に特化したツールを選択している場合、CSVまたはJSONを使用したデータ駆動型APIテストの最適なツールの比較でトレードオフがより深く掘り下げられています。これらのどれも間違っているわけではありません。ケースを作成する人と、各ケースにどれだけのカスタムロジックが必要かに応じてアプローチを一致させてください。
スケールする実践的なワークフロー
チームのルーティンの一部となると、これは次のように見えます。
まずは狭く始めます。以前に問題になったことのあるエンドポイントを1つ選びます。Apidogで、リクエストに変数を参照し、アサーションに期待される結果を含む単一のシナリオを作成します。ハッピーパス1つ、既知の失敗1つ、境界ケース1つの3行からなるCSVを構築します。アプリで3つのイテレーションすべてが期待どおりに動作するまで実行します。
次に、テストではなくデータを増やします。バグ報告が来るたびに、そのバグの原因となった入力を新しい行として、正しい期待される出力とともに追加します。バグは恒久的な回帰ケースとなり、新しいテストを書くのではなく、ファイルに1行追加しただけです。数か月で、そのファイルはエンドポイントが実際に直面する現実世界の醜さを蓄積します。
最後に、それを自動化します。`apidog run -d`コマンドをCIに組み込み、すべてのプルリクエストでテーブル全体が実行されるようにします。これで、期限切れコードパスを壊す変更がプッシュされた瞬間にビルドが失敗し、壊れた行を直接指す名前付きイテレーションが表示されます。
複合的な利点はメンテナンスです。エンドポイントのレスポンス形状が変更された場合、アサーションを一度修正すれば、すべてのケースに修正が適用されます。さらに50のケースが必要な場合は、50行追加するだけです。どれだけカバレッジが広がっても、テストロジックは単一の、小さく、読みやすいものとして維持されます。
よくある質問
パラメーター化テストとデータ駆動型テストの違いは何ですか? これらは同じアイデアを表しており、人々はこれらの用語を interchangeably(同じ意味で)使用します。どちらも、外部から提供される異なる入力と期待される出力を使用して、1つのテストを繰り返し実行することを意味します。「パラメーター化」はパラメーターバインディングメカニズムに重点を置き、「データ駆動型」は外部データソースに重点を置きます。実際には、同義語として扱ってください。
データファイルにはCSVとJSONのどちらを使用すべきですか? フォーマットをデータの形状に合わせてください。すべての行が同じ単純な列を持つフラットな表形式のケースにはCSVが適しており、CSVは非エンジニアがスプレッドシートで編集しやすいです。配列やサブオブジェクトを持つリクエストボディのように、ネストされたり構造化されたペイロードにはJSONが適しています。Apidogは両方をイテレーションデータとして読み取ることができるので、ひねくれた方法ではなく、ケースを最もよく表現できる方を選んでください。
何百ものイテレーションはパイプラインを遅くしますか? 各行はシナリオの1回の実行なので、合計時間は行数と1リクエストあたりの遅延時間に比例します。ほとんどのAPIテストでは、これは分ではなく秒単位です。もし大量のデータセットがビルドを長引かせるようであれば、分割してください。すべてのプルリクエストで高速なスモークテストのサブセットを実行し、完全なテーブルは夜間またはリリース前のジョブで実行します。同じシナリオとファイルが両方を動かし、データのサブセットだけが変わります。
データファイルとテスト設定からシークレットをどのように除外しますか? 認証情報をデータファイルから完全に除外してください。トークンとパスワードは環境変数またはCIシステムのシークレットストアに置き、`$APIDOG_ACCESS_TOKEN`などのように参照します。データファイルにはテスト入力と期待される結果を保持し、認証情報を保持すべきではありません。リポジトリを読める人なら誰でもCSVを読めるので、そのように扱ってください。
同じパラメーター化されたシナリオをステージング環境と本番環境で実行できますか? はい、できます。シナリオとデータファイルを固定し、`-e`フラグで環境を切り替えます。プルリクエストチェックをステージングに向け、デプロイ後のスモークテストを本番環境に向ける際に、同じシナリオIDとデータを使用し、異なる環境IDを指定するだけです。これが、環境とデータが別々の入力である理由の全てです。
まとめ
パラメーター化されたAPIテストは、カバレッジをコピペの作業からデータ入力のタスクへと変えます。テストを一度書き、各ケースをCSVまたはJSONファイルの行として記述し、残りはランナーに任せるだけです。ロジックは小さく読みやすく保たれ、ケースはチームの誰もがアクセスでき、CIは変更ごとにテーブル全体を実行します。
Apidogは、オーサリングのための視覚的なシナリオビルダー、データのための外部CSVおよびJSONファイル、そしてあらゆるCIシステムでのヘッドレス実行のための`apidog run -d`コマンドを提供します。1つのシナリオを構築し、それを成長するケースのテーブルに向け、誰かが行を追加するたびにテストカバレッジが広がります。Apidogをダウンロードして、次の不安定な使い捨てテストをスケーラブルなパラメーター化シナリオに変えましょう。
