Javaのテスト設計は仕様と変更点から考える
Javaのテスト設計で迷う場面は、実務ではかなり多いです。JUnitでテストを書けることと、保守しやすいテストを設計できることは別です。何を単体テストで確認するか。モックをどこまで使うか。境界値や異常系をどう選ぶか。こうした判断が、後から効いてきます。
結論から言うと、テストは実装行数をなぞるものではありません。そのため、仕様、境界、失敗時の振る舞い、変更されやすい判断を確認するために書きます。
この記事では、Java/Spring Bootの実務で使いやすい考え方を整理します。また、単体テスト、境界値、モック、結合テスト、レビューで見られやすいポイントを扱います。
単体テストはメソッドではなく振る舞いを見る
まず、単体テストは「publicメソッドごとに1つ書く」という作業ではありません。大切なのは、そのクラスやメソッドが外から見てどのように振る舞うかです。
例えば、料金計算のテストなら、内部でどのprivateメソッドを呼ぶかより、入力条件に対して正しい金額、割引、例外が返るかを確認します。実装の都合に寄せすぎると、リファクタリングのたびに壊れるテストになります。
JUnitの基本的な書き方は、公式のJUnit User Guideで確認できます。ただし、実務で重要なのはアノテーションの知識だけではありません。つまり、どの観点をテストケースとして残すかです。
class PriceCalculatorTest {
@Test
void calculatesDiscountedPriceForGoldMember() {
PriceCalculator calculator = new PriceCalculator();
Money actual = calculator.calculate(
new OrderAmount(10_000),
MemberRank.GOLD
);
assertThat(actual).isEqualTo(new Money(9_000));
}
}
このテストでは、メソッド内部の分岐ではなく「ゴールド会員なら10%割引される」という業務の振る舞いを見ています。そのため、内部実装を変えても仕様が同じならテストは残せます。
Javaのテスト設計では境界値を先に洗い出す
次に、境界値を先に洗い出します。なぜなら、テストケースを思いつきで増やすと、数は多いのに重要な条件が抜けることがあるためです。
例えば、購入金額が10,000円以上なら送料無料という仕様なら、見るべきなのは9,999円、10,000円、10,001円です。正常系を1つだけ確認しても、境界の実装ミスは見つかりません。
| 観点 | 確認する例 | 意図 |
|---|---|---|
| 境界値 | 9,999円、10,000円、10,001円 | 条件分岐の境目を確認する |
| 代表値 | 通常会員、ゴールド会員 | 主要な業務パターンを確認する |
| 異常系 | 負数、null、不正な状態 | 失敗時の振る舞いを確認する |
| 組み合わせ | 会員ランクとキャンペーン | 条件の衝突を確認する |
ただし、すべての組み合わせを網羅しようとするとテストは膨らみます。業務上の影響が大きい条件、過去にバグが出た条件、今後変更されやすい条件を優先します。
テストケースは正常系だけで終わらせない
正常系のテストだけでは、実務では不十分です。例えば、業務アプリでは、入力不備、権限不足、在庫不足、外部API失敗など、失敗時の扱いが重要になります。
特に、例外を投げるのか、エラーコードを返すのか、空の結果を返すのかは、呼び出し元の実装に影響します。ここをテストで固定しておくと、変更時のレビューが楽になります。
@Test
void throwsExceptionWhenStockIsInsufficient() {
OrderService service = new OrderService(stockRepository);
assertThatThrownBy(() -> service.placeOrder(request))
.isInstanceOf(OutOfStockException.class)
.hasMessageContaining("stock");
}
AssertJのようなアサーションライブラリを使うと、期待値を読みやすく表現できます。詳しい使い方はAssertJのドキュメントで確認できます。
なお、異常系を増やす目的は、単にカバレッジを上げることではありません。障害時や不正入力時に、システムがどのように振る舞うべきかを明確にすることです。
Javaのテスト設計でモックを使いすぎない
モックは便利ですが、使いすぎるとテストが実装詳細に張り付きます。そのため、Repository、外部API、時刻、乱数のように、テスト内で制御したい依存を置き換える用途に絞ると扱いやすいです。
Mockitoを使う場合は、公式サイトのMockito documentationも参考になります。ただし、verifyを増やしすぎると、何を保証したいテストなのかが読みにくくなります。
| 依存の種類 | モック候補か | 考え方 |
|---|---|---|
| 外部APIクライアント | 候補になる | 通信結果を固定したい |
| Repository | 単体テストでは候補 | DBを使わず業務ロジックを確認したい |
| 値オブジェクト | 基本は不要 | 本物を使った方が読みやすい |
| 同じクラス内のprivateメソッド | 不要 | 外から見える振る舞いで確認する |
例えば、ServiceのテストでRepositoryをモックするのは自然です。一方で、ドメインロジックを持つ値オブジェクトまでモックすると、テストが本来の仕様から離れてしまいます。
結合テストとの役割を分ける
単体テストで確認することと、結合テストで確認することは分けます。例えば、すべてを単体テストで確認しようとすると、モックが増えます。逆に、すべてを結合テストに寄せると、実行が遅くなり、失敗原因も追いにくくなります。
実務では、業務ロジックの分岐は単体テストで薄く速く確認します。一方で、DBマッピング、トランザクション、HTTPレスポンス、Springの設定は結合テストで確認する方が向いています。
| テスト種別 | 向いている確認 | 注意点 |
|---|---|---|
| 単体テスト | 計算、分岐、例外、境界値 | 実装詳細に寄せすぎない |
| Repositoryテスト | SQL、JPAマッピング、制約 | テストデータの管理が必要 |
| APIテスト | HTTPステータス、レスポンス形式 | ケースを増やしすぎない |
| E2Eテスト | 主要な業務フロー | 遅く壊れやすいため数を絞る |
つまり、テストの粒度は目的から決めます。速く何度も回したい確認は単体テストへ寄せ、フレームワークやDBを含めないと意味がない確認は結合テストへ寄せます。
テスト名は仕様が読める形にする
テスト名は、将来の読者に向けたドキュメントでもあります。そのため、実装者本人だけがわかる名前にすると、失敗したときに何が壊れたのか判断しにくくなります。
例えば、testCalculate よりも、calculatesDiscountedPriceForGoldMember の方が意図を読みやすいです。日本語のチームなら、表示名に日本語を使う選択肢もあります。
@Test
@DisplayName("ゴールド会員の場合は10%割引される")
void goldMemberDiscount() {
// ...
}
ただし、名前を長くすればよいわけではありません。条件、操作、期待結果が伝わる範囲で十分です。失敗時のログに出たとき、何の仕様が壊れたかがわかる名前を目指します。
Javaのテスト設計でレビュー指摘されやすい例
レビューでは、テストがあるかどうかだけでなく、変更に耐えられるかを見ます。特に、次のようなテストは、短期的には動いても保守で困りやすいです。
| 指摘されやすい例 | 起きる問題 | 見直すポイント |
|---|---|---|
| 正常系しかない | 失敗時の仕様がわからない | 異常系と境界値を追加する |
| privateメソッドを直接テストしたがる | 実装変更に弱い | publicな振る舞いで確認する |
| verifyばかりで結果を見ていない | 呼び出し順に依存する | 最終的な状態や戻り値を見る |
| 1つのテストで多くを確認する | 失敗原因が読みにくい | 観点ごとに分ける |
| テストデータが複雑すぎる | 何を確認しているか埋もれる | 必要な値だけ明示する |
特に、カバレッジの数字だけを目的にすると、意味の薄いテストが増えます。カバレッジは補助指標として使い、レビューでは「このテストでどの仕様を守っているか」を説明できる状態にします。
実務で使うJavaのテスト設計のチェックリスト
- テスト対象の仕様を一文で説明できるか?
- 正常系だけでなく、境界値と異常系を確認しているか?
- モックは外部依存や制御したい依存に絞っているか?
- 実装詳細ではなく、外から見える振る舞いを確認しているか?
- 単体テストと結合テストの役割が分かれているか?
- テスト名から条件と期待結果が読めるか?
- 失敗したときに原因を追いやすい粒度になっているか?
まずは、このチェックリストで既存のテストを見直します。そのうえで、変更が多い業務ロジックから優先してテストを整えると、効果を実感しやすくなります。
Javaのテスト設計でよくある質問
カバレッジは何パーセントを目指すべきですか?
一律の正解はありません。重要なのは、業務上壊れると困る仕様、分岐、境界値がテストされていることです。ただし、数字を目標にする場合でも、カバレッジだけで品質を判断しないようにします。
privateメソッドはテストしなくてよいですか?
基本的には、privateメソッドを直接テストするより、publicな振る舞いを通して確認します。どうしても直接確認したいほど複雑なら、責務を別クラスへ切り出すサインかもしれません。
モックを使うとテストが壊れやすいのはなぜですか?
呼び出し回数や呼び出し順を細かく検証しすぎると、実装を少し変えただけでテストが落ちます。モックは外部依存を制御するために使い、業務上の期待結果は戻り値、状態、例外で確認する方が安定します。
単体テストと結合テストはどちらを増やすべきですか?
まずは単体テストで業務ロジックの分岐を速く確認できる状態を作ります。そのうえで、DB、HTTP、Spring設定など、単体では意味が薄い部分を結合テストで補います。
まとめ:Javaのテスト設計は変更に強い仕様書を作ること
Javaのテスト設計では、JUnitやMockitoの使い方だけを覚えても不十分です。まず、何を守るためのテストなのかを決めます。仕様、境界値、異常系、外部依存、結合テストとの分担を整理することが重要です。
まずは、変更されやすい業務ロジックから見直します。次に、境界値と失敗時の振る舞いをテストで固定します。さらに、モックを使いすぎず、単体テストと結合テストの役割を分けます。この流れを守ると、レビューしやすく保守しやすいテストに近づきます。
Java/Spring Bootの実務では、テスト、例外処理、API設計、トランザクション設計がつながります。bluenaの採用情報では、Java経験を活かせる案件相談も行っています。今の経験を次の環境でどう伸ばすか、一度整理してみてください。
一度カジュアル面談をしませんか?
株式会社bluenaは「高還元」と「伴走支援」を両立したSES企業です。単価の81〜86%を還元する報酬体系と、専任サポーターによる隔週1on1で、エンジニアが納得できるキャリアを実現します。
まとまっていなくてもOK——まずは現在地を聞かせてください。
カジュアル面談ですので、お気軽にお聞かせください。





