エンドポイントに小さな変更を加えて出荷したとします。コードはコンパイルされ、新機能は動作し、デプロイも完了しました。2日後、名前を変更したフィールドが以前は文字列だったのに今はオブジェクトになっているため、モバイルクライアントがクラッシュし始めました。誰も壊すつもりはありませんでした。変更は局所的に見えました。しかし、実際にはそうではありませんでした。
APIリグレッションテストは、この種の障害がユーザーに影響する前に検出するための仕組みです。現在のAPIの動作をテストスイートとして保存し、変更のたびに再実行します。レスポンス形式、ステータスコード、主要な値がベースラインから逸脱したら、スイートを失敗させ、壊れた箇所を特定します。
この記事では、何をベースラインにするか、再利用可能なスイートをどう構築するか、CIでどう自動実行するかを実装手順ベースで説明します。
APIリグレッションテストとは何か
リグレッションテストとは、変更後も既存の動作が維持されていることを確認するために、過去に合格したテストを再実行することです。
APIの場合、「既存の動作」とは主に以下です。
- ルート
- ステータスコード
- レスポンススキーマ
- 主要フィールドの値
- OpenAPI仕様との整合性
APIはよくある変更で簡単に退行します。
- JSONフィールド名を変更した
- リファクタリングで
200が204になった - バリデーション追加で以前の入力が拒否されるようになった
- ORMアップグレードで日付フォーマットが変わった
- 依存関係更新でエラーレスポンスの形が変わった
これらはコンパイルエラーにはなりません。呼び出し元の一部だけが壊れる統合障害として表面化します。
APIリグレッションテストの対象は、アプリケーション全体ではありません。他のシステムが依存するHTTPインターフェースに絞ります。範囲を絞ることで、すべてのコミットやプルリクエストで実行しやすくなります。
何をベースラインにするか
リグレッションスイートの価値は、何をアサートするかで決まります。
アサーションが少なすぎると破損を見逃します。多すぎると、意図した変更のたびにテストが落ちます。基準はシンプルです。
クライアントが壊れる可能性があるものをアサートし、たまたま今日のレスポンスに含まれているだけの値は固定しない。
まずは次の4レイヤーをベースラインにします。
ステータスコード
既知の入力に対して、既知のステータスを返すことを確認します。200が500になるのは当然リグレッションです。201が200に変わる場合も、クライアントが依存していればリグレッションです。レスポンススキーマ
フィールド名、ネスト構造、型を確認します。文字列がオブジェクトになる、配列が単一オブジェクトになる、といった変更は代表的なサイレントブレイクです。主要フィールド値
すべての値ではなく、契約上意味を持つ値を確認します。例:idが存在する、statusが既知のenumに含まれる、totalが数値である。契約
OpenAPI仕様と実際のレスポンスが一致しているか確認します。仕様でemailが必須なのにAPIが返さない場合、それは契約違反です。詳しくは API契約テスト を参照してください。
最小構成のチェックは次のようになります。
GET /v1/users/42 -> 200
body.id is present, type number
body.email is present, type string, matches email format
body.status is one of ["active", "pending", "suspended"]
body.roles type array
response time < 800 ms
これだけでも、クライアントが依存する契約の大部分をカバーできます。createdAt のように毎回変わる値は、具体値ではなく型やフォーマットを固定します。
レスポンスボディのチェック設計については、APIアサーション も参考になります。
手動テストではなく自動化する
手動でもリグレッションテストはできます。APIクライアントを開き、保存したリクエストを再送し、レスポンスを目視確認します。
ただし、これはすぐに破綻します。
- エンドポイントが増えると確認漏れが出る
- 人間は退屈なケースをスキップしがち
- 深夜の依存関係更新や自動マージには対応できない
- 結果がレビュー可能な成果物として残りにくい
自動リグレッションテストでは、保存したスイートをマシンが毎回実行します。価値は「1回が速い」ことだけではありません。「誰かが実行を思い出さなくても、毎回実行される」ことにあります。
| 項目 | 手動 | 自動 |
|---|---|---|
| 変更ごとに実行されるか | いいえ。規律に依存 | はい。デフォルトで実行 |
| カバレッジ | 少数のエンドポイント | スイート全体 |
| 実行コスト | 人間の時間で数分 | 計算時間で数秒 |
| 深夜の破損検出 | できない | できる |
| 初期労力 | 低い | 中程度 |
実運用するAPIでは、リグレッションテストはCIで自動化しましょう。
再利用可能なリグレッションスイートを構築する
リグレッションスイートは、アサーション付きの保存済みリクエストをまとめたものです。1回作って終わりではなく、APIの成長に合わせて拡張します。
1. リソース単位でグループ化する
機能名ではなく、リソース単位でまとめます。
例:
/users
- GET /users/{id}
- POST /users
- PATCH /users/{id}
- DELETE /users/{id}
/orders
- GET /orders/{id}
- POST /orders
こうしておくと、ユーザー関連の変更時にどのスイートを見るべきかが明確になります。
2. 動的な値は環境変数にする
ベースURL、認証トークン、テナントIDなどはリクエストに直書きしません。
{{baseUrl}}/v1/users/{{userId}}
Authorization: Bearer {{token}}
環境を切り替えるだけで、同じスイートをローカル、ステージング、本番相当に対して実行できます。
3. 依存するリクエストを連結する
実際の利用フローでは、API呼び出しは単体で完結しないことが多いです。
例:
- ユーザーを作成する
- 作成レスポンスから
idを抽出する - その
idでユーザーを取得する - ユーザーを更新する
- ユーザーを削除する
このような連結フローは、単独リクエストでは見つからない退行を検出できます。これは API統合テスト と重なる領域です。
4. エッジケースはデータ駆動にする
似たリクエストを大量に複製する代わりに、入力データをテーブル化します。
email,expectedStatus
alice@example.com,201
bob@test.co,201
not-an-email,422
,422
a@b,422
この場合、1つのリクエストで5ケースを実行できます。
- 正常なメールアドレス
- 別ドメインの正常値
- 不正なメール形式
- 空文字
- 境界的な短い値
新しいバグを見つけたら、コードを増やすのではなくCSVに行を追加します。
5. スイートを高速に保つ
20分かかるスイートは、いずれスキップされます。
実行時間を抑えるために、次を意識します。
- 遅い外部依存はモックする
- 独立したリクエストは並列化する
- PRでは高速なコアスイートだけを実行する
- 重いE2Eフローは夜間実行に分ける
CIで変更ごとにスイートを実行する
ローカルPC上のリグレッションスイートは、そのPCしか守りません。重要なのは、プルリクエストやメインブランチへのマージ時にCIで自動実行することです。
基本パターンはどのCIでも同じです。
- CLIランナーをインストールする
- 保存済みスイートを指定する
- 対象環境を指定する
- アサーション失敗時にビルドを失敗させる
- レポートを出力する
Apidogでは apidog-cli を使って保存済みスイートをヘッドレス実行できます。
npm install -g apidog-cli
保存済みのシナリオまたはスイートをIDで実行します。
apidog run \
--access-token "$APIDOG_ACCESS_TOKEN" \
-t 123456 \
-e 789012 \
-r cli,html,junit
各オプションの意味は次のとおりです。
--access-token
実行を認証します。CIシークレットに保存し、リポジトリには入れません。-t
実行するシナリオ、フォルダ、またはスイートのIDです。-e
環境IDです。ステージングや本番相当など、実行対象を切り替えます。-r
レポート形式です。cliはコンソール出力、htmlはHTMLレポート、junitはCIで解析しやすいXMLを生成します。
GitHub Actionsの例
以下は、すべてのプルリクエストでAPIリグレッションスイートを実行する例です。
name: API Regression
on: [pull_request]
jobs:
regression:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '22'
- run: npm install -g apidog-cli
- run: |
apidog run \
--access-token "$APIDOG_ACCESS_TOKEN" \
-t 123456 \
-e 789012 \
-r cli,junit
env:
APIDOG_ACCESS_TOKEN: ${{ secrets.APIDOG_ACCESS_TOKEN }}
シナリオが失敗すると、ランナーはゼロ以外の終了コードを返し、ジョブは失敗します。これにより、原因が確認されるまでプルリクエストのマージをブロックできます。
コピー&ペースト可能なパイプライン例は、CI/CD用Apidog CLI と GitHub ActionsでのAPIテスト自動化 を参照してください。
データファイルを渡す
データ駆動テストでCSVやJSONを使う場合は、-d で指定します。
apidog run \
--access-token "$APIDOG_ACCESS_TOKEN" \
-t 123456 \
-e 789012 \
-d ./test-data/emails.csv \
-r cli,junit
機能ブランチ固有のAPIバージョンをテストする場合は、--branch を追加します。クラウド側に実行履歴を保持したい場合は、--upload-report を追加します。
スキーマと契約の差分を比較する
アサーションは実行時のリグレッションを検出します。一方、スキーマ差分はデプロイ前に定義上の破壊的変更を検出します。
OpenAPI仕様をリポジトリで管理している場合、プルリクエストで新旧仕様を比較できます。
安全な変更の例:
- オプションフィールドを追加する
- enumに後方互換な値を追加する
- 説明文を更新する
破壊的変更の例:
- フィールドを削除する
- フィールド名を変更する
- 型を変更する
- オプションフィールドを必須にする
- 受け入れる値を狭める
差分ツールで「user.phone が削除されます」のように表示できれば、レビューアはYAML全体を読まなくても影響を把握できます。
さらに、ライブAPIに対して契約テストを実行します。実際のレスポンスを現在のスキーマと照合し、仕様が約束しているフィールドが欠けていないか、禁止されている型が返っていないかを確認します。
つまり、二重に守ります。
- スキーマ差分:契約への意図的な編集を検出する
- 契約テスト:実装が契約から逸脱したことを検出する
破壊的変更が常にバグとは限りません。意図的にフィールドを削除することもあります。その場合は、突然テストを更新するのではなく、バージョン管理と非推奨化プロセスを通します。詳しくは 大規模APIのバージョン管理と非推奨化 を参照してください。
Apidogでリグレッションスイートを実行する
Apidogでは、API設計、テストシナリオ、アサーション、CI実行を同じワークフローにまとめられます。
基本的な流れは次のとおりです。
- APIリクエストを保存する
- ステータスコード、スキーマ、フィールド値のアサーションを追加する
- 必要に応じてレスポンスから値を抽出し、次のリクエストに渡す
- CSVまたはJSONデータセットを添付してデータ駆動ケースを作る
- 関連するシナリオをテストスイートにまとめる
-
apidog-cliでCIから実行する
API設計とテストが同じプロジェクト内にあるため、レスポンスを保存済みスキーマに対して検証できます。スキーマを別々の場所に二重管理する必要がありません。
apidog-cli の役割は、保存済みシナリオやスイートをCI上で再生し、結果を返すことです。インタラクティブなリクエスト送信ツールでも、ロードジェネレーターでもありません。範囲が明確なため、Nodeを実行できるCIステップに組み込みやすくなっています。
CLIとCIワークフローの詳細は、Apidog CLI CI/CDパイプラインガイド を参照してください。
今週から始めるスターターワークフロー
既存のテスト戦略を全面的に作り直す必要はありません。まずは次の手順で始めます。
最も呼び出される5つのエンドポイントを選ぶ
リグレッションの影響はトラフィックの多い場所に集中します。各エンドポイントに最小アサーションを追加する
ステータスコード、レスポンススキーマ、主要フィールド2〜3個を確認します。1つの連結フローを追加する
コアリソースに対して作成、読み取り、更新、削除を行います。バリデーションが多いエンドポイントにデータテーブルを追加する
正常値、空値、境界値、不正値をCSVやJSONで管理します。プルリクエスト時にCIで実行する
apidog-cliをインストールし、-r junit付きでスイートを実行します。失敗時はマージをブロックします。バグが見つかったらテストケースを追加する
見逃した本番障害は、次回から検出するための新しいテストケースにします。
ステップ1〜4は小さく始められます。ステップ5はCIファイルを1つ追加するだけです。その後は、スイートがプルリクエストごとに自動実行されます。
目標は、インシデントになり得たフィールド名変更を、本番デプロイ前の赤いチェックに変えることです。
よくある質問
APIリグレッションテストは一般的なリグレッションテストと何が違いますか?
一般的なリグレッションテストは、UIやビジネスロジックを含むシステム全体を対象にします。APIリグレッションテストは、HTTPインターフェースに絞ります。具体的には、ルート、ステータスコード、レスポンススキーマ、主要フィールド値です。範囲が狭いため高速に実行でき、他システムが依存する契約を直接検証できます。
リグレッションスイートはどのくらいの頻度で実行すべきですか?
APIに影響する可能性があるすべての変更で実行します。実務では、すべてのプルリクエストとメインブランチへのマージ時にCIで実行します。PRでは高速なコアスイートを実行し、夜間ビルドでより大きなE2Eフローを実行すると運用しやすくなります。
脆いスイートを避けるには何をアサートすべきですか?
クライアントが壊れる可能性があるものをアサートします。ステータスコード、レスポンス構造、型、ID、enum、金額など契約上意味のある値です。タイムスタンプやランダムIDのように毎回変わる値は、具体値ではなく型やフォーマットを確認します。
コードを書かずにAPIリグレッションテストを実行できますか?
はい。Apidogのようなツールでは、シナリオとアサーションをGUIで作成し、apidog-cli でCIからヘッドレス実行できます。手書きのテストハーネスを用意しなくても、保存済みスイートをパイプラインで再生できます。
意図的な破壊的変更はどう扱うべきですか?
予期しないテスト失敗として処理するのではなく、バージョン管理と非推奨化プロセスに乗せます。スキーマ差分でレビュー時に破壊的変更を明示し、必要に応じてエンドポイントをバージョンアップします。そのうえで、クライアントへ通知し、移行後にリグレッションベースラインを更新します。
Top comments (0)