REST APIテストはステータスコードの意味から始まります

RESTはステータスコードに意味を与えており、クライアントはその意味に依存しています。暗黙の前提にせず、明示的にアサーションする価値があります。

  • 作成時はlocationを伴う201であり、ボディだけで他に何もない200ではありません。

  • 存在しないリソースには404、権限がない場合は403。 権限がない場合に404を返すのは、一部のチームが意図的に選ぶ設計です。一方、存在しないリソースに403を返すのは単なるバグです。

  • 不正な入力には422または400を、一貫して返します。どちらか一方を選び、すべてのエンドポイントがそれに従っているか確認してください。

  • ボディにエラーを入れた200は決して返さないこと。 よくあるパターンですが、すべてのクライアントに特別な処理を書かせることになります。

冪等性

PUTとDELETEは冪等であるべきものです。2回呼び出しても同じ状態が保たれ、2回目でエラーが発生してはいけません。これは間違えやすい一方で、確認は簡単です。2回呼び出してアサーションするだけです。

2回目のDELETEが500ではなく404を返すべき点も同様です。クライアントはリトライするため、リトライ安全でないエンドポイントは、一瞬のネットワークの乱れをユーザーに見える障害へと変えてしまいます。

サービスが掲げる規約

ページネーション全ページをたどった結果は、コレクションが示す件数と一致するか。ページ境界での1件のずれは非常によくあります。
フィルタリングとソート未知のフィルターは拒否されるか、それとも黙って無視されるか。黙って無視する実装は、誤ったデータを自信たっぷりに返します。
部分更新PATCHは送信された項目だけを変更するか、それとも残りを黙ってnullにするか。

すべての土台となるチェック

ステータスだけでなく、ボディに対してアサーションすること。本来レコードがあるべき場所で空のリストを返す200は、これまでに書かれたあらゆるステータスのアサーションを通過してしまいます。この習慣ひとつで、REST APIテストにおいてどの規約チェックよりも多くの実際の不具合を検出できます。

4つの問題

APIテストスイートが最初の1年を生き延びられるかを決めるのは、次の4つです。期限切れになるセッション、実行時にしか存在しない値、互いに依存する呼び出し、そして誰も片付けないレコードです。TestSpriteはこれらをAuto-Authentication、Dynamic Variables、Dependency Chains、Auto-Cleanupとして扱います。これらを詳しく解説しているのが、 APIテストのドキュメント。

まず書き出しておくべき規約

APIが規約に従っているかをテストする前に、その規約が何であるかを誰かが明文化しなければなりません。そしてこの作業の過程で、多くのチームは自分たちの認識が食い違っていることに気づきます。

4つの問いでほぼ決着がつきます。作成時にどのステータスを返し、locationを含めるか。呼び出し元がいずれにせよ閲覧を許可されない場合、存在しないリソースは404か403か。未知のクエリパラメータは拒否するか、それとも無視するか。PATCHにおいて、指定されなかったフィールドは変更なしとみなすか、nullとみなすか。

どれにも普遍的な正解はなく、いずれもクライアントが依存する選択です。4つの答えを書き出す作業は20分で終わり、「RESTfulでありたい」という漠然とした意図を、実際にアサーションできるものへと変えます。これこそが、そもそもテストを成立させるための前提条件です。

すべてを書かずにカバレッジを得る

生成されたテストプランは、仕様書またはディスカバリーの結果をもとに、機能、スキーマ、認可、エラー処理、境界値の各カテゴリをカバーします。そこには上記の規約チェックの大半が標準で含まれます。

ターミナル

npm install -g @testsprite/testsprite-cli
testsprite setup

何もインストールしたくない場合は、ダッシュボードでも同じことができます。コマンドラインで可能なその他の操作をすべてまとめているのが、 CLIリポジトリ。

パイプラインが他チームの管轄である場合、最も抵抗の少ない選択肢が GitHub App です。これはwebhookであり、リポジトリには何の変更も加えません。ビルドが新バージョンの公開を通知した時点で実行されます。そうではなくリポジトリ上でチェックを見えるようにしたい場合は、 GitHub Actions のステップで実現できます。

TestSpriteによる規約のチェック方法

生成されたテストプランは、稼働中のサービスに対して機能、スキーマ、認可、エラー処理、境界値の各カテゴリをカバーします。そこには上記の規約チェックの大半が含まれます。操作ごとのステータスの意味、不正な入力の拒否、存在しないリソースに対する挙動、そして件数が合致するページネーションです。

Auto-Authenticationは実行全体を通じてセッションを維持し、Dynamic Variablesは呼び出し間で値を引き継ぎ、Dependency Chainsは各ケースが必要とするものと生成するものから実行順序を導き出し、Auto-Cleanupはその実行が作成したものだけを正確に削除します。最後の1つは、多くの人が思う以上にRESTにおいて重要です。冪等性のチェックでは、同じ破壊的な操作を意図的に2回呼び出すことになるからです。

得られるのは、文書化して守られることを願うだけの規約ではなく、変更のたびにアサーションされる自分たちの規約です。これが、他のすべてが201を返す中で1つのエンドポイントだけが黙って200を返す、という事態を防ぎます。

RESTの純粋さはテストにおいて重要ですか?

クライアントがそれに依存している部分に限ります。教科書が定める規約ではなく、サービス自身が従うと掲げている規約をテストしてください。

HATEOASはどのようにテストすればよいですか?

リンクを返しているなら、それが解決可能で、示すとおりの場所を指していることをアサーションします。返していないなら省略して構いません。本当に実装しているサービスはごくわずかです。

GraphQLの場合はどうですか?

規約がまったく異なります。ステータスコードが持つ意味ははるかに小さく、エラーは設計上ボディに含まれて返されるため、上記のチェックの大半はそのままでは適用できません。

レスポンスヘッダーはチェックすべきですか?

コンテントタイプは常にチェックします。キャッシュやレート制限のヘッダーは、クライアントがそれらに基づいて動作する場合にチェックします。アサーションを書く前に、その点を把握しておく価値があります。

バージョニングはテストする価値がありますか?

複数のバージョンをサポートしているなら、価値があります。古いバージョンが以前と同じように動作することをアサーションしてください。それこそがバージョン番号が交わす約束だからです。

要点

APIが交わす約束をテストしましょう。

REST APIテストでは、サービスが掲げる規約を検証すべきです。ステータスの意味、冪等性、ページネーションの合計件数、そして未知のフィルターが拒否されるかどうかです。そしてそのすべての土台として、ステータスではなくボディに対してアサーションしてください。