目次を表示する

TDD実践ガイド 2026

テストリストの技法 ── 「次に何をテストするか」の判断軸

本章の方針

前章で最初の Red-Green-Refactor を回した。6サイクルで Money クラスが育った。だが、正直に言おう。前章のテストの順序は筆者が事前に設計した順序だった。

実際の開発では、「次に何をテストすべきか」が分からないことの方が多い。この「次の一手がわからない」感覚が、TDD 挫折の最大の原因だ。

本章では、「次のテスト」を選ぶための具体的な技法を3つ紹介する。


技法1:テストリスト

Kent Beck が『TDD by Example』で提唱した最もシンプルな方法だ。

やり方

実装を始める前に、思いつくテストを全て書き出す。リスト形式でいい。コードにする必要はない。

Money のテストリスト(思いつくまま):
  □ 同じ金額・同じ通貨は等しい
  □ 金額が違えば等しくない
  □ 通貨が違えば等しくない
  □ 負の金額は作れない
  □ 0円は作れる
  □ 加算できる
  □ 異なる通貨は加算できない
  □ 減算できる
  □ 異なる通貨は減算できない
  □ 減算で負になるとエラー
  □ 比較できる(大小)
  □ Infinity は作れない
  □ NaN は作れない
  □ 通貨コードが不正なら作れない
  □ 小数点以下の扱い(0.1 + 0.2 問題)

順序を決める

書き出したら、簡単なものから順に並べ替える

順序付けの原則:
  1. 簡単なもの(自信があるもの)から始める
  2. 一歩で実装が進むものを優先する
  3. 複雑なもの・迷うものは後回しにする

理由:
  ・簡単なテストで「動く状態」を早く作る
  ・成功体験がリズムを生む
  ・後のテストは、先のテストの上に積み上がる

テストリストは生き物

テストリストは書いたら終わりではない。

開発中のテストリスト操作:
  ・実装中に気づいた新しいケースを追加する    → □ 追加
  ・不要と判断したケースを削除する            → ✗ 削除
  ・完了したケースにチェックを入れる          → ✓ 完了
  ・今は手をつけないケースに印をつける        → △ 保留

前章の Money は、テストリストの上半分をこなしたことになる。下半分(減算、比較、小数点)は、必要になるまで保留だ。


技法2:三角測量(Triangulation)

「テストを1本追加するたびに、実装を少しずつ汎用化していく」技法。

具体例:Money の比較

Money に大小比較を追加する場面を考える。

// 最初のテスト
test('金額が大きい方がgreaterThanでtrueを返す', () => {
  const large = Money.of(1000, 'JPY')
  const small = Money.of(500, 'JPY')
  expect(large.greaterThan(small)).toBe(true)
})

Green にする最小のコードは?

// ❌ 最小だが「ズル」な実装
greaterThan(other: Money): boolean {
  return true  // テストは通るが、常に true を返すだけ
}

テストが1本しかないと、この「ズル」な実装でも通ってしまう。ここで三角測量が効く。2本目のテストを追加する。

// 2本目のテスト:別の角度からの検証
test('金額が小さい方がgreaterThanでfalseを返す', () => {
  const large = Money.of(1000, 'JPY')
  const small = Money.of(500, 'JPY')
  expect(small.greaterThan(large)).toBe(false)
})

これで return true は通らなくなる。「ズル」ができなくなったので、正しい実装を書く。

greaterThan(other: Money): boolean {
  if (this.currency !== other.currency) {
    throw new Error('通貨が一致しません')
  }
  return this.amount > other.amount
}

三角測量の使いどころ

三角測量が有効な場面:
  ・実装の方向が明確でないとき
  ・「最小の実装」がハードコードになりそうなとき
  ・1本のテストでは仕様の意図が伝わらないとき

三角測量が不要な場面:
  ・実装の方向が明確で、自信があるとき
  →  素直に正しい実装を書いてよい

三角測量は「毎回やるべきルール」ではない。実装に自信がないとき、あるいは「ズル」な実装に引きずられそうなときに使う道具だ。


技法3:境界値分析

テストケースの中で最も効果的にバグを見つけるのは、境界値のテストだ。

境界値の見つけ方

Money を例に、境界値を体系的に洗い出す。

金額(amount)の境界値:
  ・0          → 許容される最小値
  ・-1         → 拒絶される最大値(0のすぐ下)
  ・Infinity   → 数値の上限
  ・NaN        → 数値でない
  ・0.1 + 0.2  → 浮動小数点の罠

通貨(currency)の境界値:
  ・'JPY'      → 正常な3文字コード
  ・''         → 空文字列
  ・'JP'       → 2文字(短すぎる)
  ・'JPYY'     → 4文字(長すぎる)
  ・'jpy'      → 小文字

境界値テストの書き方

describe('Money 境界値', () => {
  test('0円は生成できる', () => {
    const zero = Money.of(0, 'JPY')
    expect(zero.amount).toBe(0)
  })

  test('-1円は生成できない', () => {
    expect(() => Money.of(-1, 'JPY')).toThrow()
  })

  test('空文字の通貨コードは生成できない', () => {
    expect(() => Money.of(1000, '')).toThrow()
  })
})

等価パーティショニングとの組み合わせ

境界値分析を等価パーティショニング(同じ振る舞いをする値をグループ化)と組み合わせると、テストの網羅性と効率を両立できる。

金額のパーティション:
  ┌──────────────┬──────────────┬──────────────┐
  │  負の数       │  0以上の有限値  │  非数値        │
  │  (-∞, 0)     │  [0, ∞)      │  NaN, Infinity │
  │  → 拒絶      │  → 受理      │  → 拒絶       │
  └──────────────┴──────────────┴──────────────┘
  
  テストすべき代表値:
    負の数 → -1(境界値のすぐ下)
    0以上  → 0(境界値そのもの), 1000(典型値)
    非数値 → NaN, Infinity

テストの命名規則

テストリストが長くなると、テスト名が仕様書として機能する。読みやすいテスト名のパターンを紹介する。

パターン1:「〜した場合、〜になる」

// ✅ 条件と結果が明確
test('異なる通貨を加算した場合、エラーを投げる', ...)
test('負の金額で生成した場合、エラーを投げる', ...)
test('同じ金額・同じ通貨で比較した場合、等しいと判定する', ...)

パターン2:主語を明示する

// ✅ 「誰が」「何を」が明確
test('Money.of は負の金額を拒絶する', ...)
test('Money.add は異なる通貨の加算を拒絶する', ...)
test('Money.equals は金額と通貨の両方を比較する', ...)

アンチパターン:曖昧なテスト名

// ❌ 何をテストしているか分からない
test('Money テスト', ...)
test('正常系', ...)
test('エラーケース', ...)
test('add のテスト1', ...)

テスト名が曖昧だと、テストが失敗したときに「何が壊れたか」がすぐに分からない。テスト名は失敗時のエラーメッセージとしても機能することを意識する。


テストの構造:Arrange-Act-Assert

個々のテストの内部構造には、AAA(Arrange-Act-Assert) パターンが有効だ。

test('同じ通貨のMoneyを加算できる', () => {
  // Arrange:テストの前提条件を準備する
  const a = Money.of(1000, 'JPY')
  const b = Money.of(500, 'JPY')

  // Act:テスト対象の操作を実行する
  const result = a.add(b)

  // Assert:期待する結果を検証する
  expect(result.equals(Money.of(1500, 'JPY'))).toBe(true)
})
AAA の原則:
  ・Arrange:1テストに1つのシナリオ
  ・Act    :1テストに1つのアクション
  ・Assert :1テストに1つの検証(原則)

1テスト1アサートは理想だが、関連する複数のアサートを
1テストにまとめることは許容される(例:equals で amount と currency を両方確認)。

テストリストの実践:Subscription に向けた準備

次章で扱う Subscription(契約管理)のテストリストを、練習として作ってみよう。

Subscription のテストリスト(思いつくまま):
  □ トライアルを開始できる
  □ トライアルからアクティブに移行できる
  □ アクティブからキャンセルできる
  □ キャンセル済みからアクティブには戻れない
  □ トライアルからいきなりキャンセルできる(?)
  □ 期限切れのトライアルはアクティブにできない(?)
  □ プランを変更できる(アクティブ時のみ?)
  □ 同じプランへの変更はエラー?

がついている項目は、仕様が不明確だ。TDD では、テストを書こうとした時点で仕様の曖昧さが発見される。これは TDD の副産物として非常に価値が高い。仕様が曖昧なまま実装を始めると、後から「これどうするんだっけ?」と手戻りが発生する。


本章のまとめ

技法使いどころ効果
テストリスト実装開始前テスト対象の全体像を把握。抜け漏れを防ぐ
三角測量実装の方向が不明確なときハードコードを防ぎ、汎用的な実装に導く
境界値分析テストケースの選定時最小のテスト数で最大のバグ検出力
テスト設計の思考フロー:
  1. テストリストで全体を俯瞰する
  2. 簡単なものから順に並べる
  3. 各テストで境界値を意識する
  4. 実装に自信がなければ三角測量で補強する
  5. 開発中にリストを更新し続ける

次章では、Money よりも複雑な題材に進む。状態を持つオブジェクト──Subscription の状態遷移を、テストで駆動してみよう。