| name | test-design |
| description | テストの設計・記述に関する汎用的な考え方(プログラミング言語非依存)。テストを書く・レビューする際に使用する。 |
| user-invocable | false |
テスト設計の考え方
このガイドラインは、良いテストを設計・記述するための
言語非依存の原則をまとめたもの。
特定のテストフレームワークやビルドツールには依存しない。
言語ごとの具体的な書き方は各言語のスキル
(例: java-dev, python-dev)や tdd-workflow を参照すること。
アクティベーション・タイミング
- ユニット / 統合 / E2E テストを新規に書くとき
- 既存テストをレビュー・改善するとき
- 何をテストすべきか(テスト観点)を検討するとき
1. 何をテストするか(テスト観点)
テストは「実装が仕様を満たしていること」を保証する。
最低限、以下の観点を網羅する。
- 正常系: 期待される入力に対する正しい振る舞い
- 異常系・エラー系: 不正な入力や失敗時の振る舞い
(例外、エラー応答、ロールバック等)
- 境界値: 0、空、最大・最小、上限・下限の前後
- エッジケース: null、空文字、空リスト、
重複、順序、並行アクセスなど見落としやすいケース
カバレッジ率は目安であって目的ではない。
数値を満たすことより、
上記の観点が実際に検証されているかを重視する。
(プロジェクトで基準がある場合はそれに従う)
2. テストの種類と役割
各層は目的が異なる。どの層で何を検証するかを意識する。
- ユニットテスト: 個々の関数・メソッド・クラスを
外部依存から隔離して検証する。高速で数を多く保つ。
- 統合テスト: 複数のコンポーネントやDB・外部サービスと
組み合わせた際の連携を検証する。
- E2Eテスト: 利用者の視点で、システム全体を通した
重要なユーザーフローを検証する。数は絞る。
低層(ユニット)を厚く、高層(E2E)を薄くするのが基本
(テストピラミッド)。
遅く壊れやすい高層テストに検証を寄せすぎない。
3. 振る舞いを検証する(実装の詳細に依存しない)
テストは「何をするか(振る舞い・結果)」を検証すべきで、
「どう実現しているか(内部実装)」に依存させない。
- 公開されたインターフェース(公開API・戻り値・
観測可能な副作用)を通して検証する
- 内部の private なメソッドやフィールド、
特定のアルゴリズムの内部状態を直接検証しない
こうすることで、内部実装をリファクタリングしても
テストが壊れにくくなる(brittle test の回避)。
良い: 公開された振る舞いを検証する
order = Order(price=100)
total = service.calculateTotal(order)
assert total == 90 # 10%割引が適用された「結果」を検証
避ける: 内部実装(private フィールド等)を検証する
assert service.discountRate == 0.1
4. テストの独立性
各テストは他のテストに依存せず、
単独でも、どの順序でも、繰り返しても同じ結果になること。
- テスト間で状態(DB、グローバル変数、ファイル等)を
共有しない。共有する場合は各テストの前後で確実に初期化する
- 実行順序に依存させない
- 共通のセットアップは「前処理フック」
(BeforeEach 相当)に集約し、各テストの独立性を保つ
避ける: 前のテストの結果に依存する
test_1_createUser()
test_2_updateUser() # test_1 が作ったデータに依存
良い: 各テストが必要な状態を自分で用意する
beforeEach: repository.deleteAll()
参考として、良いユニットテストの性質は
FIRST(Fast / Independent / Repeatable /
Self-validating / Timely)で覚えられる。
5. モック化の方針
プロジェクト方針が異なる場合を除き、
古典派(Classical School) のアプローチを重視する。
モックの使用はシステム境界に限定し、
内部のドメインロジック同士の結合には
実オブジェクトを使用する。
モック化するもの(システム境界)
- 外部依存: データベース(Repository)、
外部APIクライアント、ファイルシステム、メッセージング
- アダプター: 外部システムと接続する
インターフェースの実装
- 不安定な要素: 現在時刻、乱数、
環境依存の値など制御したいもの
モック化しないもの
- 自分のドメインロジック同士の結合。
実オブジェクトを組み合わせて検証する
実オブジェクトの組み合わせが煩雑になりすぎるケースや、
モックを使う既存コードに合わせたほうが良いケースは、
都度検討してよい。
6. テストケースの記述と可読性(ビジネスストーリーの表現)
テストケースの名称や説明は、 テストの説明を読むだけで
「システムがどのような仕様を満たしているか」が理解できるように記述する。
自然言語による説明の記述
テストフレームワークが提供する自然言語の記述機能を積極的に活用する。
- 自然言語での命名:
JUnitの
@DisplayName など、テストケースに自然言語で
タイトルを付与できる機能がある場合は、必ずこれを活用する。
- コメントでの補足:
そのような機能がない場合やメソッド名だけでは不十分な場合は、
テストメソッドのコメント(JavaDocやDocstring等)を活用し、
簡潔な説明を日本語で記述する。
内部用語を避け、ビジネスストーリーとして記述する
テストケースを記述する際、変数名、クラス名、型、null や empty といった
「コード内部の用語」を使用することは基本的には避ける。
ユーザーやシステムが「どのような状況で」「何をしたら」「どうなるか」という
ビジネス仕様(ユースケース)に焦点を当てて記述する。
| 避けるべき記述(内部用語) | 良い記述(ビジネスストーリー) |
|---|
userId が null のとき NullPointerException が発生すること | 未ログインのゲストユーザーがマイページにアクセスした場合はエラーになること |
orderList が empty のとき、calculateTotal が 0 を返すこと | ショッピングカートが空の状態で決済に進んだ場合、合計金額が0円になること |
user.status が ACTIVE かつ age が 20 以上のとき | アクティブな成人ユーザーが会員向けサービスを利用できること |
7. 避けるべきアンチパターン
内部ロジックをすべてモックにする
ロンドン学派的に「すべての依存をモックにする」手法は、
リファクタリングでテストが壊れやすくなる。
プロジェクト方針で定められている場合を除き避ける。
実装の詳細をテストする
内部のプライベート変数の状態などを直接検証しない
(→ セクション3参照)。
テストの隔離ができていない
テスト間でデータが共有され実行順序に依存する
(→ セクション4参照)。
アサーションのないテスト
実行して例外が出ないことだけを確認し、
結果を検証していないテストは価値が低い。
必ず期待する結果をアサートする。
意図の伝わらないテスト名
何を検証しているか名前から分からないテストは避ける。
「対象 / 条件 / 期待結果」が伝わる命名を心がけるとともに、
セクション6に記載された「ビジネスストーリー」に沿った説明を付与する。
成功のメトリクス
- 正常系・異常系・境界値・エッジケースが検証されている
- すべてのテストが成功(Green)
- スキップされたテストがない
- ユニットテストが高速(1テストあたり数十ms 未満が目安)
- クリティカルなユーザーフローがテストで保護されている
忘れないでください: テストはオプションではない。
自信を持ったリファクタリング、迅速な開発、
そして本番環境の信頼性を支えるための「命綱」である。