| name | testing |
| description | テストの記述ガイドライン。テストを書くとき、テスト設計を考えるとき、テスト戦略を議論するときに使用する。テストの目的、構成、書き方の原則に基づき、信頼性が高く保守しやすいテストを書く。 |
Testing
テストの目的は「コスト削減」ではなく、高い信頼性の実行結果に短い時間で到達し、開発者に確信を与え、ソフトウェアの継続的な成長を可能にすること。
テストの目的
テストでは品質は上がらない。品質を上げるのはプログラミング。テストは品質が保たれていることを確認する手段。
テストがもたらす価値は3つ:
- 意思決定のための信号 — この変更をマージしてよいか、デプロイしてよいか、どこに問題があるか
- リファクタリングの安全網 — 振る舞いを固定することで、内部の書き換えを恐れず行える
- 実行可能な仕様書 — テスト名と内容が、コードの意図と契約を説明する
「あとで書く」テストは、これらの価値のうち(2)と(3)を大きく損なう。振る舞いを決める前にテストを書くと、テストが仕様を駆動する。
TDD のリズム
Red → Green → Refactor を小さく速く回す:
- Red: 失敗するテストをまず書く(期待する振る舞いを記述)
- Green: テストを通す最小限の実装を書く(汚くていい)
- Refactor: テストを通したまま、重複を消し設計を整える
「動作するきれいなコード」は1度に書かない。動作させる → きれいにする の2段階に分ける。
実装を導く3つのパターン
Green に進むときの選択肢:
| パターン | やり方 | 使う場面 |
|---|
| 仮実装 (Fake It) | 期待値をベタ書きで返す | 最初の一歩、不安なとき |
| 三角測量 (Triangulation) | 2つ目・3つ目のテストで一般化を強制 | 仮実装からの脱却、複雑な分岐 |
| 明白な実装 (Obvious Implementation) | いきなり本実装を書く | ロジックが自明で自信があるとき |
不安なら仮実装から。自明なら明白な実装で。途中で詰まったら一段階戻す。
テストケースの設計
全てのケースを試すのは不可能。バグが潜みやすい所に優先して書く。
境界値分析
不具合は境界(0、1、最大、最小、空、オーバーフロー)に集まる。n < 10 のテストなら、9, 10, 11 を試す。
同値分割
入力空間を「同じ振る舞いをするはずのグループ」に分け、各グループから代表を1つ選ぶ。全網羅より、クラス分けして代表を押さえる方が効率的。
壊れる可能性で濃淡をつける
すべてのコードに同じ密度でテストを書かない。複雑な分岐・外部境界・過去に壊れた所・仕様変更の多い所にテストを集中する。トリビアルな getter/setter にテストはいらない。
テストサイズ
テストを分類するときは、スコープ(単体/統合/E2E)よりもサイズで考える。
| サイズ | 制約 | 特性 |
|---|
| Small | 単一プロセス、メモリ内 | 高速、決定的、安定 |
| Medium | 単一マシン(DB、ファイル可) | やや遅い、不安定性のリスク |
| Large | 制約なし(外部サービス可) | 遅い、不安定、高コスト |
テストピラミッド: Smallを多く、Mediumを中程度、Largeを少なく。アイスクリームコーン(Largeが多い状態)は避ける。
SMURF指標
テスト設計のトレードオフを評価する5軸:
- Speed: 実行速度
- Maintainability: 保守性
- Usage of resources: リソース使用量
- Reliability: 信頼性(決定性、安定性)
- Fidelity: 忠実性(本番環境との類似度)
全てを最大化はできない。プロジェクトの状況に応じてバランスを取る。
テストを書く原則
テスト失敗の信頼性を守る
テストが失敗したとき、それは本物の問題であるべき。
- 偽陽性(False Positive): 問題ないのに失敗する → テストへの信頼が失われる
- 偽陰性(False Negative): 問題があるのに成功する → テストの存在意義がない
偽陽性が多いテストスイートは、やがて無視されるようになる。
実装詳細ではなく振る舞いをテストする
expect(component.state.count).toBe(1);
expect(screen.getByText('1')).toBeInTheDocument();
自作自演テストを避ける
テストコードにプロダクトコードと同じロジックを書かない。
const expected = (130 / (1 + 8 / 100)) * (8 / 100);
expect(item.taxAmount()).toBe(expected);
expect(item.taxAmount()).toBe(9.629629629629629);
アサーションは具体的に書く
expect(result === 40).toBe(true);
expect(result).toBe(40);
テストは独立させる
テスト間の実行順序に依存しない。各テストが単独で実行できる状態を保つ。
Flaky Testを放置しない
結果が不安定なテスト(同じコードで成功/失敗が変わる)は即座に対処する。放置するとテストスイート全体の信頼性が崩壊する。
テストの構成
Arrange-Act-Assert(AAA)
const cart = createCart();
cart.addItem({ name: 'Widget', price: 100 });
const total = cart.getTotal();
expect(total).toBe(100);
1テスト1検証
1つのテストで1つの振る舞いを検証する。複数の検証を詰め込まない。テスト名は検証する振る舞いを表現する。
テスト容易性の設計
テストが書きにくいコードは、設計に問題がある兆候。テスト容易性を下げている要素を特定し、切り出す(Humble Objectパターン)。
テスト容易性を下げる要素:
- 外部API呼び出し
- 現在時刻への依存
- ファイルシステムへの依存
- データベースへの依存
- 乱数への依存
これらを境界に押し出し、コアロジックをテストしやすくする。
テストダブルの使い方
詳細は references/test-doubles.md を参照。
リファレンス