テストの種類と戦略
ストーリー
「テストにはいくつかの種類がある。 全てを理解した上で、戦略的にテストを配置することが大事だ」
松本先輩がホワイトボードにピラミッドを描いた。
「テストピラミッド。これがテスト戦略の基本だ。 底辺が厚く、頂点が薄い。この形を覚えておけ」
テストの種類
1. ユニットテスト(単体テスト)
関数やクラスなど、最小単位のコードを個別にテストします。
typescript
// テスト対象
function add(a: number, b: number): number {
return a + b;
}
// ユニットテスト
describe('add', () => {
it('正の数同士の加算', () => {
expect(add(2, 3)).toBe(5);
});
it('負の数を含む加算', () => {
expect(add(-1, 5)).toBe(4);
});
it('0 との加算', () => {
expect(add(0, 0)).toBe(0);
});
});| 特徴 | 内容 |
|---|---|
| 対象 | 関数、メソッド、クラス |
| 速度 | 非常に高速(ミリ秒) |
| 依存 | 外部依存はモックで置換 |
| 信頼性 | 高い(決定的) |
| コスト | 低い |
2. インテグレーションテスト(結合テスト)
複数のコンポーネントが正しく連携するかテストします。
typescript
// インテグレーションテスト: サービス + リポジトリ
describe('UserService + Database', () => {
let userService: UserService;
beforeAll(async () => {
await setupTestDatabase();
userService = new UserService(new UserRepository());
});
it('ユーザーを作成して取得できる', async () => {
const created = await userService.createUser({
name: '田中太郎',
email: 'tanaka@example.com',
});
const found = await userService.findById(created.id);
expect(found?.name).toBe('田中太郎');
expect(found?.email).toBe('tanaka@example.com');
});
afterAll(async () => {
await cleanupTestDatabase();
});
});| 特徴 | 内容 |
|---|---|
| 対象 | 複数コンポーネントの連携 |
| 速度 | 中程度(秒 単位) |
| 依存 | DB、外部APIなどの実際の依存を使用 |
| 信頼性 | 中程度(環境依存の場合あり) |
| コスト | 中程度 |
3. E2Eテスト(エンドツーエンドテスト)
ユーザーの操作をシミュレートし、システム全体をテストします。
typescript
// Playwright による E2E テスト
import { test, expect } from '@playwright/test';
test('ユーザーがログインして注文できる', async ({ page }) => {
// ログイン
await page.goto('/login');
await page.fill('[data-testid="email"]', 'user@example.com');
await page.fill('[data-testid="password"]', 'password123');
await page.click('[data-testid="login-button"]');
// 商品をカートに追加
await page.goto('/products/1');
await page.click('[data-testid="add-to-cart"]');
// 注文完了
await page.goto('/cart');
await page.click('[data-testid="checkout"]');
await expect(page.locator('[data-testid="order-success"]')).toBeVisible();
});| 特徴 | 内容 |
|---|---|
| 対象 | システム全体(UI → API → DB) |
| 速度 | 遅い(分単位) |
| 依存 | 全てのシステムが必要 |
| 信頼性 | 低い(フレーキーになりやすい) |
| コスト | 高い |
テストピラミッド
╱╲
╱ ╲
╱ E2E╲ 少ない(重要シナリオのみ)
╱ 10% ╲
╱────────╲
╱結合テスト╲ 中程度
╱ 20% ╲
╱──────────────╲
╱ ユニットテスト ╲ 多い(関数・クラス単位)
╱ 70% ╲
╱────────────────────╲
速い ←─── 実行速度 ───→ 遅い
安い ←─── コスト ───→ 高い
高い ←─── 安定性 ───→ 低い
なぜピラミッド型が最適なのか
| 層 | 割合 | 理由 |
|---|---|---|
| ユニットテスト | 70% | 高速、安定、保守しやすい、問題の特定が容易 |
| インテグレーションテスト | 20% | コンポーネント間の連携を検証 |
| E2Eテスト | 10% | 重要なユーザーシナリオのみ。遅くて不安定 |
アンチパターン:逆ピラミッド(アイスクリームコーン)
┌────────────────────┐
│ 手動テスト │ ← 大量の手動テスト
├────────────────────┤
│ E2Eテスト │ ← 大量のE2E
├──────────┤
│ 結合テスト │
├────┤
│UT │ ← ユニットテストがほぼない
└────┘
→ テスト実行が遅い、不安定、保守コストが高い
テスト戦略の設計
何をどのレベルでテストするか
ユニットテストでテストすべき:
├── ビジネスロジック(計算、バリデーション)
├── Value Object の振る舞い
├── ユーティリティ関数
└── 状態遷移のロジック
インテグレーションテストでテストすべき:
├── DB との CRUD 操作
├── API エンドポイントの入出力
├── 外部サービスとの連携
└── 認証・認可のフロー
E2Eテストでテストすべき:
├── ユーザー登録〜ログイン
├── 商品検索〜購入の一連の流れ
├── 決済処理の正常完了
└── クリティカルなビジネスフロー
まとめ
| テスト種類 | 対象 | 速度 | コスト | 推奨割合 |
|---|---|---|---|---|
| ユニットテスト | 関数・クラス | 非常に高速 | 低 | 70% |
| インテグレーションテスト | コンポーネント連携 | 中程度 | 中 | 20% |
| E2Eテスト | システム全体 | 遅い | 高 | 10% |
チェックリスト
- 3種類のテストの違いを説明できる
- テストピラミッドの形と理由を説明できる
- 逆ピラミッド(アイスクリームコーン)の問題を理解した
- 何をどのレベルでテストすべきか判断できる
次のステップへ
テストの種類を理解したら、次はテストケース設計技法を学びます。境界値分析、同値分割、デシジョンテーブルなどの体系的な手法を身につけましょう。
推定読了時間: 25分