ストーリー
松本先輩がホワイトボードにピラミッドを描いた。
テストの種類
1. ユニットテスト(単体テスト)
関数やクラスなど、最小単位のコードを個別にテストします。
// テスト対象
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. インテグレーションテスト(結合テスト)
複数のコンポーネントが正しく連携するかテストします。
// インテグレーションテスト: サービス + リポジトリ
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テスト(エンドツーエンドテスト)
ユーザーの操作をシミュレートし、システム全体をテストします。
// 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) |
| 速度 | 遅い(分単位) |
| 依存 | 全てのシステムが必要 |
| 信頼性 | 低い(フレーキーになりやすい) |
| コスト | 高い |
テストピラミッド
graph TD
E2E["E2Eテスト<br/>10%<br/>少ない(重要シナリオのみ)"]
INT["結合テスト<br/>20%<br/>中程度"]
UNIT["ユニットテスト<br/>70%<br/>多い(関数・クラス単位)"]
E2E --- INT --- UNIT
classDef e2e fill:#f8d7da,stroke:#dc3545,color:#000
classDef int fill:#fff3cd,stroke:#ffc107,color:#000
classDef unit fill:#d4edda,stroke:#28a745,color:#000
class E2E e2e
class INT int
class UNIT unit
速い <--- 実行速度 ---> 遅い / 安い <--- コスト ---> 高い / 高い <--- 安定性 ---> 低い
なぜピラミッド型が最適なのか
| 層 | 割合 | 理由 |
|---|---|---|
| ユニットテスト | 70% | 高速、安定、保守しやすい、問題の特定が容易 |
| インテグレーションテスト | 20% | コンポーネント間の連携を検証 |
| E2Eテスト | 10% | 重要なユーザーシナリオのみ。遅くて不安定 |
アンチパターン:逆ピラミッド(アイスクリームコーン)
graph TD
MANUAL["手動テスト<br/>← 大量の手動テスト"]
E2E["E2Eテスト<br/>← 大量のE2E"]
INT["結合テスト"]
UT["UT<br/>← ユニットテストがほぼない"]
MANUAL --- E2E --- INT --- UT
classDef bad fill:#f8d7da,stroke:#dc3545,color:#000
classDef warn fill:#fff3cd,stroke:#ffc107,color:#000
classDef small fill:#e2e3e5,stroke:#6c757d,color:#000
class MANUAL bad
class E2E bad
class INT warn
class UT small
テスト実行が遅い、不安定、保守コストが高い
テスト戦略の設計
何をどのレベルでテストするか
ユニットテストでテストすべき:
├── ビジネスロジック(計算、バリデーション)
├── Value Object の振る舞い
├── ユーティリティ関数
└── 状態遷移のロジック
インテグレーションテストでテストすべき:
├── DB との CRUD 操作
├── API エンドポイントの入出力
├── 外部サービスとの連携
└── 認証・認可のフロー
E2Eテストでテストすべき:
├── ユーザー登録〜ログイン
├── 商品検索〜購入の一連の流れ
├── 決済処理の正常完了
└── クリティカルなビジネスフロー
まとめ
| テスト種類 | 対象 | 速度 | コスト | 推奨割合 |
|---|---|---|---|---|
| ユニットテスト | 関数・クラス | 非常に高速 | 低 | 70% |
| インテグレーションテスト | コンポーネント連携 | 中程度 | 中 | 20% |
| E2Eテスト | システム全体 | 遅い | 高 | 10% |
チェックリスト
- 3種類のテストの違いを説明できる
- テストピラミッドの形と理由を説明できる
- 逆ピラミッド(アイスクリームコーン)の問題を理解した
- 何をどのレベルでテストすべきか判断できる
次のステップへ
テストの種類を理解したら、次はテストケース設計技法を学びます。境界値分析、同値分割、デシジョンテーブルなどの体系的な手法を身につけましょう。
推定読了時間: 25分