LESSON 25分

テストケース設計技法

ストーリー

「テストを書くとき、なんとなく値を選んでないか?」

松本先輩が問いかけた。

「えっと......適当に100とか200とか入れてます」

「それだとバグを見逃すことがある。テストケース設計には 体系的な技法がある。これを使えば、少ないテストケースで 多くのバグを見つけられるようになる」


1. 境界値分析(Boundary Value Analysis)

バグは境界値(ちょうどの値やその前後)で発生しやすいという経験則に基づく手法です。

基本的な考え方

仕様: 1〜100 の範囲の数値を受け付ける

境界値:
  ←── 範囲外 ──→ ←──── 範囲内 ────→ ←── 範囲外 ──→
  ... -1  0  │ 1   2  ...  99  100 │ 101  102 ...

テストすべき値:
  0   (範囲外の境界)
  1   (範囲内の境界 - 最小値)
  2   (範囲内の境界 + 1)
  99  (範囲内の境界 - 1)
  100 (範囲内の境界 - 最大値)
  101 (範囲外の境界)

実践例:年齢バリデーション

typescript
// 仕様: 0歳〜150歳を有効とする
function validateAge(age: number): boolean {
  return age >= 0 && age <= 150;
}

// 境界値分析に基づくテスト
describe('validateAge - 境界値分析', () => {
  // 下限の境界
  it('age = -1 は無効', () => expect(validateAge(-1)).toBe(false));
  it('age = 0 は有効', () => expect(validateAge(0)).toBe(true));
  it('age = 1 は有効', () => expect(validateAge(1)).toBe(true));

  // 上限の境界
  it('age = 149 は有効', () => expect(validateAge(149)).toBe(true));
  it('age = 150 は有効', () => expect(validateAge(150)).toBe(true));
  it('age = 151 は無効', () => expect(validateAge(151)).toBe(false));
});

2. 同値分割(Equivalence Partitioning)

入力値を「同じ振る舞いをするグループ(同値クラス)」に分割し、各グループから代表値を選んでテストする手法です。

基本的な考え方

仕様: 成績判定
  90〜100点: A
  70〜89点:  B
  50〜69点:  C
  0〜49点:   D
  それ以外:  エラー

同値クラス:
  [-∞, -1]  → エラー(無効クラス1)
  [0, 49]   → D(有効クラス1)
  [50, 69]  → C(有効クラス2)
  [70, 89]  → B(有効クラス3)
  [90, 100] → A(有効クラス4)
  [101, +∞] → エラー(無効クラス2)

各クラスの代表値: -5, 30, 60, 80, 95, 120

実践例

typescript
function getGrade(score: number): string {
  if (score < 0 || score > 100) throw new Error('Invalid score');
  if (score >= 90) return 'A';
  if (score >= 70) return 'B';
  if (score >= 50) return 'C';
  return 'D';
}

// 同値分割に基づくテスト
describe('getGrade - 同値分割', () => {
  it('負の値はエラー', () => {
    expect(() => getGrade(-5)).toThrow('Invalid score');
  });

  it('0-49点はD', () => expect(getGrade(30)).toBe('D'));
  it('50-69点はC', () => expect(getGrade(60)).toBe('C'));
  it('70-89点はB', () => expect(getGrade(80)).toBe('B'));
  it('90-100点はA', () => expect(getGrade(95)).toBe('A'));

  it('101以上はエラー', () => {
    expect(() => getGrade(120)).toThrow('Invalid score');
  });
});

3. デシジョンテーブル(Decision Table)

複数の条件の組み合わせによって結果が変わる場合に、全パターンを漏れなくテストする手法です。

基本的な考え方

仕様: 送料の計算
  条件1: 会員かどうか
  条件2: 注文金額が5,000円以上か
  条件3: 離島かどうか

デシジョンテーブル:
┌──────────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 条件      │ R1 │ R2 │ R3 │ R4 │ R5 │ R6 │ R7 │ R8 │
├──────────┼────┼────┼────┼────┼────┼────┼────┼────┤
│ 会員      │ Y  │ Y  │ Y  │ Y  │ N  │ N  │ N  │ N  │
│ 5000円以上│ Y  │ Y  │ N  │ N  │ Y  │ Y  │ N  │ N  │
│ 離島      │ Y  │ N  │ Y  │ N  │ Y  │ N  │ Y  │ N  │
├──────────┼────┼────┼────┼────┼────┼────┼────┼────┤
│ 送料      │500 │ 0  │1000│500 │1000│500 │1500│800 │
└──────────┴────┴────┴────┴────┴────┴────┴────┴────┘

実践例

typescript
function calculateShipping(
  isMember: boolean,
  orderAmount: number,
  isRemoteIsland: boolean,
): number {
  const BASE_SHIPPING = 800;
  let shipping = BASE_SHIPPING;

  if (isMember) shipping -= 300;
  if (orderAmount >= 5000) shipping -= 300;
  if (isRemoteIsland) shipping += 500;

  return Math.max(shipping, 0);
}

// デシジョンテーブルに基づくテスト
describe('calculateShipping - デシジョンテーブル', () => {
  it('R1: 会員 + 5000円以上 + 離島 → 500円',  () => {
    expect(calculateShipping(true, 6000, true)).toBe(700);
  });
  it('R2: 会員 + 5000円以上 + 通常 → 0円', () => {
    expect(calculateShipping(true, 6000, false)).toBe(200);
  });
  it('R4: 会員 + 5000円未満 + 通常 → 500円', () => {
    expect(calculateShipping(true, 3000, false)).toBe(500);
  });
  it('R6: 非会員 + 5000円以上 + 通常 → 500円', () => {
    expect(calculateShipping(false, 6000, false)).toBe(500);
  });
  it('R8: 非会員 + 5000円未満 + 通常 → 800円', () => {
    expect(calculateShipping(false, 3000, false)).toBe(800);
  });
  // 全8パターンをテスト
});

技法の使い分け

技法最適な場面
境界値分析数値の範囲チェック年齢、金額、数量
同値分割入力値のグループ化成績判定、カテゴリ分類
デシジョンテーブル複数条件の組み合わせ料金計算、権限制御

組み合わせて使う

実際のテスト設計では、これらの技法を組み合わせて使います。

1. 同値分割で入力のグループを特定
2. 各グループの境界値を分析
3. 条件の組み合わせをデシジョンテーブルで整理
4. テストケースを作成

まとめ

技法目的ポイント
境界値分析境界付近のバグを検出ちょうどの値、1つ前、1つ後
同値分割効率的なテストケース選定各グループの代表値を選ぶ
デシジョンテーブル条件の組み合わせを網羅全パターンを表で整理

チェックリスト

  • 境界値分析の手法を使ってテストケースを設計できる
  • 同値分割の手法で入力値をグループ化できる
  • デシジョンテーブルを作成して全パターンを網羅できる
  • 3つの技法を組み合わせて使う方法を理解した

次のステップへ

テストケース設計技法を学んだら、次はTDD(テスト駆動開発)の基本を学びます。テストを先に書く開発手法の考え方を理解しましょう。


推定読了時間: 25分