PostmanとNewmanによるCI/CDでのAPIテスト自動化:本番環境での6ヶ月間の実績

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

金曜午後5時のデプロイの悲劇

6ヶ月前、私たちのエンジニアチームは「金曜午後のデプロイ」という恐怖の中にいました。私たちは42のマイクロサービス and 150以上のエンドポイントを管理していました。ユニットテストは常にパス(グリーン)していましたが、ステージング環境はほぼ毎週のように壊れていました。JSONキーの変更、HTTPステータスコードの変更、あるいはヘッダーの欠落。こうした些細な変化は、ロジックを切り離して検証するユニットテストでは見逃されてしまい、サービス間の実際の契約(コントラクト)をチェックできていなかったのです。

特に印象的な出来事があります。認証(Auth)サービスのマイナーアップデートによって、チェックアウトフロー全体がハングアップし、気づくまでにコンバージョン率が12%も低下しました。ユニットテストはパスしていました。なぜなら、トークン生成のロジック自体は正しかったからです。

しかし、APIのレスポンス構造がわずかに変更されたことで、フロントエンドがユーザーIDを解析できなくなっていました。金曜の夜に4.5時間を費やして、ロールバックとデバッグを行いました。それが私たちの限界でした。デプロイプロセスの中で、APIをアクティブで統合されたシステムとして検証する必要があると確信したのです。

なぜAPIのデグレードが見逃され続けたのか

事後分析(ポストモーテム)の結果、ワークフローにおける3つの主要な欠陥を特定しました。1つ目は、手動テストが足かせになっていたことです。QAリーダーは素晴らしいPostmanコレクションを持っていましたが、すべてのプルリクエストに対して手動で実行するのは不可能でした。時間がかかり、ヒューマンエラーも避けられません。締め切りに追われると、最初に省略されるのがこの作業でした。

2つ目は、開発者と運用担当者が異なる言語で話していたことです. 開発者はローカルのデバッグにPostmanを使用していましたが、そのテストは彼らのラップトップの中に留まっていました。DevOpsが管理するCI/CDパイプラインからは、これらのテストが全く見えていませんでした。APIがどう振る舞うべきかという「暗黙知」がコードベースにコミットされず、UIの中に閉じ込められていたのです。

3つ目は、エンドツーエンド(E2E)テストが重すぎたことです。Seleniumを試しましたが、テストは不安定で、実行に25分もかかっていました。私たちが必要としていたのは、APIレイヤーに対する「外科手術的」なツールでした。すべてのコミットで実行できるほど高速でありながら、リクエスト/レスポンスのサイクルにおける破壊的変更をキャッチできるほど深い検証が必要だったのです。

選択肢の比較:手動、スクリプト、それともNewman?

私たちは、決定を下す前に3つの道を検討しました。手動でのPostman実行を続けるという選択肢は除外されました。テストが自動化されていないなら、現代のDevOps環境では存在しないも同然です。単純にスケールしません。

次に、すべてをPytestやJestで書き直すことを検討しました。プログラムによるフレームワークは強力ですが、二重の手間が発生します。開発者はすでにAPI構築のためにPostmanを使っていました。別の言語で別のテストスイートを維持することを強いるのは、生産性への「税金」のように感じられました。また、QAチームがJavaScriptやPythonを深く学ばなければ貢献できないという問題もありました。

そこでNewmanの登場です。NewmanはPostmanランナーのコマンドライン版です。UIで作成したのと同じコレクションを、ターミナルから実行できます。1週間のテストを経て、Newmanは私たちのミッシングリンクとなりました。作成のためのPostmanの直感的なUIと、パイプラインに必要なCLIのパワーを兼ね備えていたのです。

ブループリント:CI/CDにおけるPostman + Newman

このワークフローを習得することは、盲目的にコードをリリースするか、検証済みの製品を届けるかの違いを生みます。私が過去180日間で洗練させてきたシステムは、厳格な「エクスポート、実行、統合」パターンに従っています。

ステップ1:Postmanコレクションの強化

アサーション(検証)は譲れません。単にレスポンスが返ってくるかを確認するだけでなく、データを検証する必要があります。Postmanでは「Tests」タブを使用します。私たちのスイートのすべてのリクエストは、現在、ステータスコードとJSONスキーマを検証しています。

// 重要なAPIアサーション
pm.test("200 OKステータスを検証", function () {
    pm.response.to.have.status(200);
});

pm.test("スキーマの整合性を検証", function () {
    const jsonData = pm.response.json();
    pm.expect(jsonData).to.have.property('id');
    pm.expect(jsonData.status).to.eql('active');
});

これらのコレクションと環境変数をJSONファイルとしてエクスポートします。これらをリポジトリ内の /tests/api ディレクトリに保存します。これにより、エンジニアがAPIのコントラクトを変更した際に、同じコミットでテストも更新されるようになります。

ステップ2:Newmanによるローカル検証

Gitにプッシュする前に、デグレードが発生していないかローカルでテストを実行します。NewmanにはNode.jsが必要で、npm経由でインストールできます。

# Newmanをインストール
npm install -g newman

# コレクションを実行
newman run ./tests/api/orders_api.json -e ./tests/api/staging_env.json

Newmanは失敗時にゼロ以外の終了コードを返します。これが「キルスイッチ」となります。テストが失敗すると終了コードは1になり、CI/CDパイプラインは即座に停止します。これにより、壊れたコードがユーザーに届くのを未然に防ぎます。

ステップ3:GitHub Actionsへの統合

コードがステージング環境にデプロイされた瞬間にAPIテストがトリガーされるよう、GitHub Actionsを設定しました。以下が実際に使用しているYAML設定です。

name: API統合テスト
on: [push]

jobs:
  test-api:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Node.jsのセットアップ
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Newmanをインストール
        run: npm install -g newman

      - name: スモークテストの実行
        run: |
          newman run ./tests/api/smoke_tests.json \
          --env-var "base_url=${{ secrets.STAGING_URL }}" \
          --reporters cli,junit \
          --reporter-junit-export results.xml

--env-var フラグに注目してください。これを使用してシークレットや動的なURLを注入します。これにより、同じコレクションファイルをローカル、ステージング、本番の各環境で使い回すことができます。

現場で得た苦労の結晶の教訓

12,000回以上の自動実行を経て、基本的なドキュメントだけでは不十分であることがわかりました。まず、レポートが重要です。開発者はCLIの出力を好みますが、ステークホルダーには視覚的な情報が必要です。私たちは newman-reporter-htmlextra プラグインを使用しています。これはインタラクティブなHTMLレポートを生成し、失敗したテストの完全なリクエスト/レスポンスログを含めることができるため、1回の失敗につき少なくとも30分のデバッグ時間を節約できています。

# 拡張レポーターをインストール
npm install -g newman-reporter-htmlextra

# 視覚的な出力で実行
newman run collection.json -r cli,htmlextra --reporter-htmlextra-export ./reports/api_report.html

2つ目は、シークレットを守ることです。Postmanの環境JSONにAPIキーを直接コミットしてはいけません。{{ADMIN_KEY}} のようなプレースホルダーを使用し、実行時にGitHub Secrets経由で実際の値を注入します。

3つ目は、テストを階層化することです。プッシュのたびに300個の回帰テストを実行するのはやりすぎです。私たちは、90秒で終了する「スモークテスト」スイート(8〜10個のクリティカルパス)を使用しています。「フル回帰テスト」は、本番環境への最終デプロイ用に取っておきます。

結果

Newmanによる自動化は、私たちの文化を変えました。パイプラインが本番環境へのリリース前にコントラクトの不整合をキャッチしてくれるため、金曜日のデプロイを恐れることはもうありません。開発者は自信を持ってリリースし、QAチームはボタンをクリックする作業から高度なテストスイートを設計する役割へと移行しました。投資対効果(ROI)はすぐに現れました。もしデプロイ後に無事を祈っているような状況であれば、検証はNewmanに任せるべき時です。

Share: