最初のテストケースを書く
期待結果、ネガティブテスト、境界値、同値クラス。
ここまでは、リクエストを送って返ってきたものを見てきました。テストが始まるのは、送る前に何が返ってくるべきかを決め、 それと比べるときです。その決定を書き留めたものがテストケースです。このレッスンでは、テストケースの書き方と、どれを書くかの 選び方を扱います。ありうるリクエストをすべて送ることはできないからです。
テストケースに含まれるもの
チームによってテンプレートは違いますが、ほとんどは同じ部分を持っています。
| 部分 | 書く内容 | 例 |
|---|---|---|
| ID | 話題にするための短い参照 | PROD-GET-03 |
| タイトル | 何を確かめるかを 1 行で | 未知の商品 ID は 404 を返す |
| 前提条件 | 始める前に成り立っていなければならないこと | ID 9999 の商品は存在しない |
| リクエスト | メソッド、URL、ヘッダー、ボディ | GET /api/Products/9999 |
| 期待結果 | ステータス、重要なヘッダー、ボディ | 404、メッセージに ID 9999 を含む JSON のボディ |
| 実際の結果 | 実行したときに記入する | 期待どおり |
期待結果は、うまく書けていないことが多い部分です。「エラーを返す」は期待結果ではありません。ほとんど何でも当てはまって しまうからです。ステータスコード、大事なヘッダー(たいていは Content-Type、ときにキャッシュや Location のヘッダー)、そしてボディ(どのフィールドがあるべきか、その型と値)について具体的に書きます。 正しい答えが何か言えないなら、開発者やプロダクトオーナーへの質問が見つかったということで、何かをテストする前に聞いておく 価値があります。
期待結果はどこから来るのでしょうか。ドキュメント、機能を説明したチケット、同じ API の似たエンドポイントの振る舞い、 そしてときには常識(価格がマイナスであるべきではない)です。これらの情報源が食い違うときは、どれに従ったかを書き留めます。
ポジティブテストとネガティブテスト
ポジティブテストは、正しいリクエストを送って動くことを確かめます。商品 1 は存在するので、GET /api/Products/1 はそれを返します。ネガティブテストは、API が拒否すべきものを送り、きちんと拒否することを確かめます。正しいステータス、 役に立つメッセージ、そして副作用がないことです。
初心者は機能の説明に沿って書くので、ポジティブテストばかりになりがちです。開発者はふつう、作業を渡す前にポジティブな 経路を自分で試しています。何かが見つかるのはネガティブテストの方です。目安として、入力を受け取るエンドポイントでは、 ポジティブテストよりネガティブテストを多く書くことになると考えてください。
同値クラス
一覧のエンドポイント GET /api/Products は limit パラメーターを受け付けます。API リファレンスには、これは 1 から 100 までのページサイズだと書かれています。送れる値は無限に あります。同値分割とは、サーバーが同じように扱うべき入力をグループにまとめ、すべてではなく各グループから 1 つずつ値をテストすることです。
| クラス | 値の例 | 期待 |
|---|---|---|
| 有効、範囲内 | limit=10 | 商品 10 件 |
| 範囲より下 | limit=-5 | 拒否、または最小値に丸める |
| 範囲より上 | limit=500 | 拒否、または 100 に丸める |
| 数値でない | limit=abc | 400 で拒否 |
| 整数でない | limit=2.5 | 400 で拒否 |
| 空 | limit= | 指定なしとして扱う |
| 指定なし | limit なし | ドキュメントどおり、すべての商品 |
「拒否、または丸める」という項目に注目してください。範囲外で何が起きるかはドキュメントに書かれていないので、筋の通った 振る舞いが 2 つあります。どちらにも理があります。テストするのは、API がそのどちらかを一貫して行い、落ちたりおかしなものを 返したりしないことです。
境界値
バグは範囲の端に集まります。開発者が <= のつもりで < と書いてしまうのがそこだからです。境界値分析とは、境界とその両側に 1 歩ずつずらした値をテストすることです。1 から 100 の範囲なら、0、1、2、99、100、101 になります。この API がこれらの値やほかのクラスを実際にどう扱うかを見てみましょう。X-Per-Page ヘッダーは、サーバーが使ったページサイズを示します。
| リクエスト | ステータス | 返った件数 | X-Per-Page |
|---|---|---|---|
?limit=0 | 200 | 1 | 1 |
?limit=1 | 200 | 1 | 1 |
?limit=-1 | 200 | 1 | 1 |
?limit=100 | 200 | 20(全件) | 100 |
?limit=101 | 200 | 20(全件) | 100 |
?limit=abc | 200 | 20 | 20 |
?limit=2.5 | 200 | 20 | 20 |
?limit= | 200 | 20 | 20 |
/api/Products?limit=0結果を読むと、1 未満の値は 1 に、100 を超える値は 100 に丸められ、整数でないものは limit がないものとして無視されています。どれもどこにも書かれていませんが、一貫しているので、おそらく意図的です。それでも 2 つのことはテスト報告に書き残す価値があります。limit=0 で 1 件返るのは意外です。0 件を求めたクライアント(たとえば総数だけを手軽に読みたい場合)が、欲しくない商品を受け取ってしまいます。また limit=abc が黙って無視されるということは、クライアントの打ち間違いが見過ごされるということです。400 なら捕まえられたはずです。
ここにはテストデータについての教訓もあります。この API には商品が 20 件しかないので、limit=100 と limit=101 は同じボディを返します。ボディからは 101 が丸められたことはわかりませんでした。わかったのはヘッダーのおかげです。境界がデータより大きいときは、このような別の手がかり を見つけるか、境界に届くだけのレコードを作りましょう。
境界は ID にも潜んでいる
リファレンスでは、/api/Products/{id} の id は int32、つまり最大値が 2,147,483,647 の 32 ビット整数とされています。これはテストする価値のある境界です。
| リクエスト | ステータス | ボディ |
|---|---|---|
/api/Products/20 | 200 | 最後の商品である商品 20 |
/api/Products/21 | 404 | JSON のエラーメッセージ |
/api/Products/0 | 404 | JSON のエラーメッセージ |
/api/Products/-1 | 404 | JSON のエラーメッセージ |
/api/Products/2147483647 | 404 | JSON のエラーメッセージ |
/api/Products/2147483648 | 404 | 空 |
/api/Products/abc | 404 | 空 |
/api/Products/1.5 | 404 | 空 |
/api/Products/01 | 200 | 商品 1 |
目につくことが 2 つあります。有効な int32 でないものにはボディのまったくない 404 が返り、有効だが存在しない ID には JSON のエラーが返ります。そして 01 は 1 として受け付けられます。後者はここでは無害ですが、URL ごとにレスポンスをキャッシュする API では、/1 と /01 は別々にキャッシュされてしまいます。どちらの結果も明らかな間違いではありませんが、どちらも知っておく価値があります。
1 つのエンドポイントのためのテストケース一式
まとめると、GET /api/Products/{id} のための最初のテストケース一式として筋がよいのは次のようなものです。テストできることのすべてではありませんが、各クラスを 1 回ずつと、各境界をカバーしています。
| ID | リクエスト | 期待結果 |
|---|---|---|
| PROD-GET-01 | GET /api/Products/1 | 200、JSON、id は 1、title は空でない文字列、price は 0 より大きい数値 |
| PROD-GET-02 | GET /api/Products/20 | 200、初期データで最大の ID |
| PROD-GET-03 | GET /api/Products/21 | 404、ID 21 を含む JSON のエラーボディ |
| PROD-GET-04 | GET /api/Products/0 | 404、JSON のエラーボディ |
| PROD-GET-05 | GET /api/Products/-1 | 404、JSON のエラーボディ |
| PROD-GET-06 | GET /api/Products/abc | 400 または 404、JSON のエラーボディ付き |
| PROD-GET-07 | GET /api/Products/2147483648 | 400 または 404、JSON のエラーボディ付き |
| PROD-GET-08 | HEAD /api/Products/1 | GET と同じステータスとヘッダー、ボディなし |
| PROD-GET-09 | GET /api/Products/1 を 2 回 | 2 回とも同じボディ |
PROD-GET-06 と 07 は、ボディが空なので現在の API では失敗します。PROD-GET-08 も失敗します。HEAD /api/Products/1 は 405 Method Not Allowed を返しますが、/api/Books/{id}サインインして表示 のような新しいリソースは HEAD に対応しています。ここで HEAD への対応が期待されるかどうかは、API の担当者に尋ねるべき問いです。それでかまいません。テストケースは起きるべきことを 記録し、失敗するテストケースは、それが起きていないことに気づく手段なのです。PROD-GET-02 には弱点があります。誰かが商品 20 を削除したり商品 21 を追加したりすると、書かれている意味を持たなくなります。次のレッスンでは、ほかの人が管理するレコードにテストが依存しないよう、 自分でテストデータを作る方法を紹介します。
バグ報告を書く
テストケースが失敗したら、それを報告にまとめます。よいバグ報告があれば、開発者はあなたに何も尋ねずに 1 分以内に問題を再現できます。上の空の 404 を例に、まず 2 つのレスポンスを比べてみましょう。
HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8
{"status":404,"error":"Not Found","message":"Product with ID 9999 does not exist."}HTTP/1.1 404 Not Found
Content-Length: 0報告は、たとえば次のようになります(多くのチームと同じく英語で書いた例です)。
Title: GET /api/Products/{id} returns an empty 404 for non-numeric
and out-of-range IDs, instead of the JSON error body
Environment: https://api.testingapis.com, 30 Sep 2026
Steps:
curl -i https://api.testingapis.com/api/Products/9999
curl -i https://api.testingapis.com/api/Products/abc
curl -i https://api.testingapis.com/api/Products/2147483648
Expected:
All three return 404 with Content-Type: application/json and a body
like {"status":404,"error":"Not Found","message":"..."}, as documented
under "Responses and errors" in the API reference.
Actual:
9999 404, JSON body with a message
abc 404, Content-Length: 0, no Content-Type, no body
2147483648 404, empty body (one above the int32 maximum; 2147483647
returns the JSON error)
Impact:
Clients that parse every error body as JSON fail on the empty
responses. The empty 404 also looks like "this URL does not exist"
rather than "this product does not exist".
Notes:
Probably the route only matches int32 values, so other input never
reaches the controller. Could be intended; if so, the docs should say
which errors have no body.この報告が役に立つ理由は次のとおりです。
- タイトルが何がどこでおかしいかを言っているので、50 件のチケットの一覧の中でもはっきりわかる。
- 手順は貼り付けられるコマンドになっている。「不正な ID で商品のエンドポイントを呼ぶ」のような書き方ではない。
- 期待される振る舞いが情報源(ドキュメント)を指しているので、単なる好みではない。
- 1 つの例ではなくパターンを示している。比較用に動くものを含めた 3 つのリクエストは、失敗する 1 つのリクエストより多くを語る。
- 本物のクライアントへの影響を述べている。開発者は影響で優先順位を決めるので、「一貫していない」だけではめったに直されない。
- 不確かさを認めている。その振る舞いは意図的かもしれず、報告はどちらの場合でも解決になること(コードを直すか、ドキュメントを 直すか)を示している。
演習
GET /api/Books/{id}サインインして表示 か GET /api/Countries/code/{code}サインインして表示 を選び、API リファレンスでそのエンドポイントを調べてから、次のことをしてください。
- パスパラメーターの同値クラスを挙げる。
- 境界値を選ぶ。国コードなら、長さと大文字・小文字について考える。
- 上の表の形式で、具体的な期待結果を持つテストケースを少なくとも 8 つ書く。
- curl で実行し、実際の結果を記入する。
期待と違う振る舞いがあれば、上の構成でバグ報告を書いてください。確信が持てない点も含めて書きます。