「テストしておいて」と言われたものの、正しい値を入力して一度動かす以外に何を試せばよいか分からない。そんなときは、仕様を「入力」「事前状態」「操作」「期待結果」へ分けるところから始めます。コードの分岐を眺める前に、利用者に保証したい約束を書き出すのが出発点です。
この記事では架空の問い合わせ管理を題材に、件名の文字数、担当者の権限、状態の変更からケースを作ります。JUnitの書き方へ進む前に、何を確かめればよいかが分かる表を完成させましょう。
仕様を入力と期待結果へ変換する

共通の題材は、件名が必須で1〜100文字、状態が下書き・受付済み・対応中・完了、利用者には投稿者と担当者がいる問い合わせ管理です。ただし、これだけではテストの答えは決まりません。「文字数」はどう数えるか、空白だけの件名は許されるか、誰がどの状態へ変更できるかが足りないからです。
この記事の練習では、次を追加の架空仕様として決めます。現場の仕様として無断で採用するものではありません。
- 件名はnull、空文字、空白だけを拒否する。前後の空白を自動削除せず、Unicodeコードポイントで1〜100と数える。
- 「対応開始」は現在の担当者だけができる。投稿者という理由だけでは許可しない。
- 受付済みの問い合わせを対応中へ変更する。他の状態からの対応開始は拒否する。
- 拒否した場合、元の状態は変えない。エラーの表示文やHTTPステータスは、実システムの別の契約として決める。
これで、実行結果を見てから正解を決めることを避けられます。仕様が未確定なら、その行の期待結果は「要確認」です。現在のコードが受け付けたから正常とするのは、既存の不具合を正解にする可能性があります。
同じ扱いになる値をまとめ、代表を選ぶ
1〜100文字が許可されるなら、その範囲のすべての文字数を最初から試す必要はありません。許可される値と拒否される値を、同じように処理されると考えられるまとまりに分け、代表値を選びます。これが同値分割の考え方です。
ただし「拒否される値だから一つでよい」とまとめすぎないでください。nullは値がない状態、空文字は長さ0、空白だけは文字数があっても件名として認めない状態です。別の条件なので、別に確認します。Javaでの区別はisEmptyとisBlankの違いも参考になります。
テスト技法の定義は、ISTQBのFoundation Levelシラバス4.0.1の4.2節で確認できます。ここで作る表は、その考え方を問い合わせの独自仕様へ当てはめたものです。
境界では、許可と拒否が切り替わる前後を見る
上限100文字なのに、100未満と実装してしまうと、100文字だけが不正に拒否されます。逆に101以下なら、101文字を誤って許可します。普通の長さで成功しただけでは、この違いは分かりません。
今回は境界とその両隣を選び、下限周辺の0・1・2文字、上限周辺の99・100・101文字を試します。これは3値境界値分析の考え方に沿った選び方です。0文字は空文字のケースと重なるので、目的を注記した一つのケースで確認できます。
| 仕様・観点 | 具体的な入力 | 期待結果 |
|---|---|---|
| 値が必要 | null | 拒否 |
| 下限より小さい | 空文字(0文字) | 拒否 |
| 下限とその隣 | a、aa | どちらも許可 |
| 上限の内側と上限 | aを99個、100個 | どちらも許可 |
| 上限を超える | aを101個 | 拒否 |
| 空白だけを拒否 | 半角空白、全角空白 | どちらも拒否 |
| 数え方の確認 | 😀を100個、101個 | 100個は許可、101個は拒否 |
最後の行は、文字数の境界とは別に、数え方が合意どおりかを確かめるものです。JavaのString.length()はUTF-16のコード単位数を返すため、今回の仕様で使うコードポイント数とは一致しない場合があります。Java 17のString公式仕様を確認すると、値を選ぶ理由も説明できます。
コードポイントと、人が一文字と感じる単位も常に一致しません。結合文字や複数のコードポイントからなる絵文字まで「見た目の一文字」で数える要件なら、今回の仕様をそのまま使わず、数え方を別に決めます。
権限と状態は、事前条件を揃えて比較する
件名を試せたら、次に対応開始を確認します。「担当者なら成功」の一行だけでは、投稿者が誤って操作できる不具合や、完了した問い合わせを再び対応中にする不具合を見逃します。
練習では投稿者をID 10、担当者をID 20、無関係の利用者をID 30とします。すべて架空の値です。
| 現在の状態 | 操作する人 | 操作 | 期待結果 |
|---|---|---|---|
| 受付済み | 担当者20 | 対応開始 | 対応中になる |
| 受付済み | 投稿者10 | 対応開始 | 拒否し、受付済みのまま |
| 受付済み | 無関係の30 | 対応開始 | 拒否し、受付済みのまま |
| 下書き | 担当者20 | 対応開始 | 拒否し、下書きのまま |
| 対応中・完了 | 担当者20 | 対応開始 | 拒否し、それぞれ元の状態のまま |
| 受付済み・担当者未設定 | 利用者20 | 対応開始 | 拒否し、受付済みのまま |
この表の重要な部分は「変わらないこと」も期待結果にした点です。エラーメッセージが出ていても、その前にDBを更新していたら仕様を満たしません。保存処理があるシステムでは、応答に加えて保存後の状態も確認対象になります。
最初は、権限だけが違うケースと状態だけが違うケースを用意すると、不具合の切り分けがしやすくなります。その後、条件の組合せで結果が変わる箇所を追加します。今回の独立検証では4状態×3利用者の12通りと、担当者未設定の1通りを試しました。この13通りだけで、あらゆる権限や状態変更を網羅したという意味ではありません。
ケースを実行できる粒度へ仕上げる
「101文字でエラー」とだけ書くより、「受付済みの問い合わせを担当者20で操作し、件名にaを101個入れ、保存を押す。件名エラーが表示され、元の件名と状態は変わらない」と書くほうが、別の人も実行できます。保存操作のケースなら、画面、API、DBのどこを観測するかも添えます。
ケースごとに、仕様の根拠、準備するデータ、操作、期待結果、実際の結果を記録します。根拠には仕様書の節や合意したIssueなどを使えます。「期待結果」と「実際の結果」は別欄にしてください。失敗したケースを、実際の動きに合わせて合格へ書き換えないためです。
今回、件名の判定11件と権限・状態13件は、Java 17.0.8の独立した判定コードで実行して通過しました。DB更新やHTTP応答、ブラウザでの保存は含みません。この確認は、練習仕様と判定コードの対応までです。保存先へつないだら「拒否時に更新されない」をその境界でも確認する必要があります。
期待結果が決まらないケースは、仕様確認の成果にする
たとえば「投稿者と担当者が同じ人ならどうなるか」「完了後に再開できるか」「前後の空白を取り除いてよいか」は、コードだけでは決めない問いです。担当者としての権限があればよいのか、別の承認が必要なのかを、具体例を添えて確認します。
テスト表に疑問が残るのは、作成に失敗したからとは限りません。実装前に未決事項を見つけた価値があります。質問を送るときは「この入力・状態のときに、どの結果を保証するか」を一つずつ示すと、仕様を決める人も判断しやすくなります。
ケースが決まった後の実装は、ServiceをJUnit・Mockitoでテストする方法へ進めます。数値の端や空の要素を扱う別の例として、List・配列の範囲外アクセスも役立ちます。
テストの価値を深めるための一冊
ケースを作れるようになり、何をテストとして残すかを考えたい人には『単体テストの考え方/使い方』を選書候補として紹介します。これはC#の例を使う本で、JUnitの操作入門ではありません。出版社の紹介と、実務担当の本棚から内容を確認できます。本人の読後体験は根拠としていません。
本棚にはアフィリエイトリンクを含みます。記事の表は本を購入せずに使えます。手元の仕様を一つ選び、まず「どの入力・状態で、何が起き、何が変わらないか」の一行を作ってみてください。
