開発者テスト入門 - テストとは編
はじめに
本テキストでは、 Spring Boot の詳細設計からテストまでを担当するエンジニアを対象に、開発者テストの基本的な考え方を紹介します。
補足:開発者テスト(Developer Testing)
本テキストが主に扱うのは「開発者テスト」です。これは、開発者が日々の開発の中で実施するテストであり、
単体だけでなく、必要に応じて結合テストや @SpringBootTest を用いた統合寄りの自動テストも含みます。
ISTQB でいうテストレベル(コンポーネント/結合/システム等)とは別の軸(実施者の軸)なので、混同しないように注意してください。
対象読者
- Spring Boot を触ったことがあり、テストの基本的な考え方を体系的に学びたい人
- テストの基本について整理したい人
- テストのアプローチ(ブラックボックス/ホワイトボックス)を理解し、テストに活かしたい人
到達目標
- Spring Boot のテストレベル(単体/結合/統合/E2E)とアノテーションの対応を理解する
- テストの目的の代表例(設計を駆動するテスト、回帰を防ぐテスト)を理解する
- テストの観点(ブラックボックス/ホワイトボックス)を理解し、テストに活かす方法を知る
- テストの基本的な書き方(Given-When-Then パターン)を理解する
- テストの基本的な手法(同値分割、境界値分析など)を理解し、実践できるようになる
次のものはスコープの範囲外とします。
- テストの実装方法(JUnit/MockK/Mockito などの具体的なコード例)
- 非機能テスト(性能/負荷/セキュリティテストなど)
- テスト自動化の運用(CI/CD でのテスト実行や、テストの失敗対応など)
- 外部結合テスト(API 結合、E2E などの実装方法)
テストレベルと Spring Boot アノテーションの目安
- 単体:Spring 起動なし。ドメイン/サービスのロジック中心
- 結合:Spring の一部だけ起動(
@WebMvcTest,@DataJpaTest,@MybatisTest,@JdbcTest) - 統合:
@SpringBootTest - E2E:起動+HTTP+外部連携も含め、ユーザー視点で通す
- 非機能:性能/負荷/セキュリティ(本編では触れません)
これらはあくまで目安であり、プロジェクトやチームによっては異なる定義がされることもあります。 ※運用上は、テストレベル(単体/結合/統合/E2E)とは別に、テストサイズ(Small/Medium/Large)で区別することもあります。
また、Spring Boot にはテスト用アノテーションが複数用意されており、 単体テストに限らず結合テストや統合テストなど幅広いテストを扱えます。
本テキストで扱うテストの狙い(開発での使われ方の例)
本テキストでは、次のようなテストの狙いを想定して説明します。 これらはあくまで代表例であり、プロジェクトやチームによってはこれ以外の狙い(探索、調査、監視など)もあります。
設計を駆動するテスト(TDD)
- テスト駆動開発(TDD:Test-Driven Development)で書かれるテスト
- 先にテストコードを書いてから実装コードを書くスタイル
- テストコードが設計のドキュメントとしても機能する
- この順番で検討することで、「コードの使われ方」や「テストのしやすさ」を意識した設計が促される
回帰を防ぐテスト
- バグ修正や機能追加の際に、既存の機能が壊れていないことを確認するためのテスト
- 既存のコードに対する変更が、他の部分に悪影響を与えていないことを保証する
- 意図した差分以外で、既存の契約(仕様)が破られていないことを確認する
何に基づいてテストを設計・評価するか(利用する情報)
ブラックボックステスト
ブラックボックステストは、内部構造ではなく仕様(要求・契約)に基づいてテストケースを設計・評価するアプローチです。
- 仕様(要求)を基準にテストケースを作る
- どうやるか(How)ではなく、何が起きるか(What)を基準に期待結果を書く
- 実装変更に強く、リファクタリングで壊れにくい
- 入力に対し、観測可能な結果(振る舞い)が期待通りになることを確認する
注意点:
- 実装の分岐を見ないため、網羅漏れ(特に境界・例外)が起きやすい
→ 境界値分析・同値分割・状態遷移などの「テストケース設計技法」と併用する
ホワイトボックステスト
ホワイトボックステストは、内部実装(制御フロー/例外パス/状態遷移/アルゴリズム)を参照して、テストケースの設計やテスト結果の評価をするアプローチです。
- 条件分岐・例外パス・境界・状態など、内部構造を手掛かりにテストケースを補強する
- カバレッジ(命令/分岐/条件 など)を「不足の発見」に使い、漏れの有無を評価する
- (手動の場合)デバッガ等で内部状態を観察して、想定した経路を通っているかを確認することもある
ホワイトボックスは漏れを見つけやすい一方で、内部手順や途中状態を期待結果として固定すると、実装変更(リファクタリング)で壊れやすくなりがちです。
そのため本テキストでは、ホワイトボックスは主に「分析(漏れ確認)」として用い、テストの実装(期待結果の表現)はブラックボックス寄りに保つことを推奨します。
注意点:
- ホワイトボックスでも「期待結果」は仕様(振る舞い)の言葉で書くと壊れにくい
テストケースの作成はブラック的に・テストケースの分析はホワイト的に
本テキストでは、テストを 「作る」 と 「漏れを探す」 に分けて考えます。
-
作成(ブラック):仕様・要求・契約(What)を基準に、入力→観測可能な結果(戻り値/例外/公開状態/外部への書き込み)で期待結果を書く
→ リファクタリングに強く、壊れにくいテストになる -
分析(ホワイト):実装を見て、分岐・例外・境界・状態の観点で不足ケースを探す(必要ならカバレッジも“不足発見”に使う)
→ ブラックだけだと抜けやすい例外・境界の漏れを埋められる
運用のコツは 「ホワイトで見つけて、ブラックの形で追加する」 ことです。
つまり、追加するテストの期待結果は「分岐を通すため」ではなく、**仕様の言葉(振る舞い)**で表現します。
※ private呼び出しや途中変数の値のアサートなど、内部手順の固定はテストを壊れやすくしがちなので、基本は避けます。
テストの基本的な書き方
Arrange-Act-Assert パターン
次の 3 フェーズでテストコードを書くことが推奨されています。
- 準備(Arrange)
- 実行(Act)
- 検証(Assert)
各テストケースは、この 3 フェーズを明確に分け、各フェーズを複数書かないことが原則です。
※ フェーズがひとつというだけで、例えば、 assert がひとつしかないという意味ではない。
また、同じ考え方は別の呼び名(Given-When-Then など)でも紹介されますが、本テキストでは AAA に用語を統一します。
テストコードにロジックを持たせない
- if は書かない
- ループは書かない
- 境界値を大量に回す場合、パラメタライズドテストでデータ駆動にする
テストで確認することはひとつの振る舞いだけ
- ひとつのテストケースで確認するのはひとつの振る舞いだけ
- 振る舞い: Given の条件で When したときに、観測可能な結果(戻り値/状態/例外/外部への書き込み)が期待通りになること
- 1テスト1振る舞い: 失敗原因を1つに絞れる粒度で書くこと(
assertを1個にせよ、ではない)
テストケース設計の技法
テストケース設計では、「どんな入力・状態・組み合わせをテストすべきか」を体系的に洗い出します。 闇雲にケースを増やすのではなく、漏れを減らしつつ、必要十分な数に絞るのが目的です。
本テキストでは、次の代表的なテストケース設計技法を紹介します。
同値分割
入力を「同じ結果になりそうなグループ(同値分割)」に分け、各グループから代表値を選んでテストする手法です。
- 目的:ケース数を抑えつつ、仕様の網羅漏れを減らす
- 手順:
- 入力条件(値の範囲、形式、必須/任意など)を列挙する
- 条件ごとに「妥当なクラス」「不正なクラス」に分割する
- 各クラスから代表値を選ぶ(通常は1つずつ)
例(年齢の入力:0~120が妥当):
次のように「妥当」と「不正」をそれぞれ同値なグループとして扱い、それぞれのグループから代表値を選びます。
- 妥当:0~120 のどれか(通常は代表値として、0、1、119、120などを選ぶ)
- 不正:-1以下 / 121以上 / 数値でない / null(必須なら)
同値分割するときは「妥当」「不正」をセットで考えると漏れにくくなります。
境界値分析
バグが入りやすい「境界(端)」に着目してテストする手法です。 同値分割の補強として使われます。
- 目的:境界付近の off-by-one(1つズレ)や比較演算ミスを早期に検出する
- 典型パターン:
- 最小値
- 最小値プラスマイナス 1
- 最大値
- 最大値プラスマイナス 1
- 0, 1, -1(符号やゼロを扱う場合)
- 空, 1件, 最大件数(コレクション・文字列長・ページング)
例(0~120が妥当):
次のように、典型パターンに沿って、境界値を中心にテストケースを選びます。
- -1(最小-1)
- 0(最小)
- 1(最小+1)
- 119(最大-1)
- 120(最大)
- 121(最大+1)
境界値は「仕様に書かれた境界」だけでなく、「実装上の境界(桁数、丸め、型変換)」も意識すると効果的です。
その他テストケース設計(発展)
- 状態遷移テスト:状態マシンの状態と遷移を網羅する
- 役立つとき: 状態依存の振る舞いが多い場合
- ステータスを持つドメインが中心(申請→承認→却下、注文→支払い→出荷など)
- 「ある状態ではできる/できない」が要件に多い
- 事故が「不正遷移」「遷移漏れ」で起きがち
- 役立つとき: 状態依存の振る舞いが多い場合
- ペアワイズテスト:複数パラメータの組み合わせを効率的に網羅する
- 役立つとき: パラメータの組み合わせが多い場合
- 条件/フラグ/設定が多い(例:権限×プラン×機能フラグ×言語…)
- クエリ条件が多い検索API(filter/sort/optionが増殖)
- 環境差(DB種別、OS、ブラウザ)×機能が多い
- 役立つとき: パラメータの組み合わせが多い場合
まずは同値+境界で設計し、状態依存や組み合わせ爆発が出たら追加で検討してください。
まとめ
本テキストでは、次のポイントを押さえてテストの基本と少々の Spring Boot との関連を紹介しました。
- Spring Boot のテストは、単体に限らず結合/統合/E2E まで幅広いレベルで考える。
- 「単体/結合」などの用語は揺れやすいので、必要に応じてテストサイズ(Small/Medium/Large)でも整理する。
- テストはブラックボックス(仕様の振る舞い)で書き、ホワイトボックス(分岐・例外・境界・状態)は漏れ確認に使う。
- テストケース設計は、まず同値分割と境界値を押さえる(状態遷移・ペアワイズは必要になったら)。
テストは「安心して変更できる状態」を作るための技術です。まずは小さく、でも確実に積み上げていきましょう。