Skip to content

Green 🟢 - テストを通過させる効率的アプローチ

TDDプロセスでは、テストリスト(TODOリスト)から1つのテストケースを選び、そのテストをパスさせるための実装を行います。本記事では、テストをパスさせるための効率的な実装アプローチについて解説します。

TDD における「実装」とは?

TDDプロセスのうち「実装」のステップで使われるのが、以下の 3つの典型的なアプローチです。

手法内容メリット使う場面
明確な実装最初から正解を書く時間効率がよく、簡潔ロジックが明白なとき
仮実装動かすためだけの一時的なコード最小実装を強制できる最小限から始めたいとき
三角測量テストを増やして一般化へ導くテストが導く設計になる本格的なロジックに向かう途中段階

これらは状況や問題の複雑さに応じて使い分けることで、効率的かつ堅牢なTDDプロセスを実現することができます。
実際の開発では、これらのそれぞれのアプローチを組み合わせて使用することが一般的です。

明確な実装(Obvious Implementation)

テストを通過させるために、直接的かつ素直な方法でコードを書くアプローチです。
解決策が明白で、どのように実装すべきかが明らかな場合に使用します。 すでに答えが明確で、そのまま正解をストレートに書く実装です。

特徴

  • 問題の解決方法が明確である場合に使用
  • 単純な関数やロジックに適している
  • 迅速に実装できる

使うタイミング

  • ロジックがシンプルで、考えるまでもないとき
  • 例えば add(2, 3) → 5 のような、仕様が直感的な場合

typescript
// テスト
test('2つの数値を足し算する', () => {
  expect(add(2, 3)).toBe(5);
});

// 明確な実装
function add(a: number, b: number): number {
  return a + b;
}

仮実装(Fake implementation / Sham implementation)

一時的に「動くけど中身が本物ではない」コードを書く方法です。 コードでまずベタ書きの値を使い、実装を進めつつ、徐々に変数に置き換えるなど、ハードコーディングや条件付きで値を返すことが多いです。

テストを通過させるために、一時的な固定値やハードコードした値を返すシンプルな実装を行うアプローチです。
これは、まず最も単純な方法でテストを通過させ、その後リファクタリングによって本来の実装に置き換えていく戦略です。

特徴

  • 最も単純な方法でテストを通す
  • ハードコードした値を返すことが多い
  • 徐々に本来の実装に進化させる

使うタイミング

明確な実装を続けている中で、予期しないレッドバーを目にした場合、仮実装に切り替える。

使う理由

  • TDD のサイクルを止めずに進めるため
  • 余計なロジックを書くのを防ぎ、「最低限」のコードで済ませる

以下のように、add(2, 3) に対応するよう「5」を返すだけ。

typescript
// テスト
test('2つの数値を足し算する', () => {
  expect(add(2, 3)).toBe(5);
});

// 仮実装(最初のステップ)
function add(a: number, b: number): number {
  return 5; / テストが通るように、仮で決め打ちし、ハードコードした値を返す
}

// リファクタリング後の本来の実装
function add(a: number, b: number): number {
  return a + b;
}

三角測量(Triangulation)

一般的な実装に進む前に、複数のテストケースを作成することで、より確実な実装を導き出すアプローチです。
一つのテストケースだけでは偶然通過する可能性がありますが、複数のテストケースを使用することで、より堅牢な実装を見つけ出せます。

複数の異なる観測点から対象の位置を正確に特定する「測量手法」になぞらえて、\複数のテストを通じて「正しい実装位置(一般化)」を導き出すという意味です。

複数のテストケースを増やして、一般化した実装へ導きます。
最初は仮実装で通し、異なる値のテストケースを追加して、共通化できる実装へ進化させていきます。

特徴

  • 複数のテストケースを使用して実装を導き出す
  • 一般化された解決策を見つけるために使用
  • 偶然通過する可能性を減らす

typescript
// 最初のテスト
test('2と3の足し算は5になる', () => {
  expect(add(2, 3)).toBe(5);
});

// 2つ目のテスト
test('5と7の足し算は12になる', () => {
  expect(add(5, 7)).toBe(12);
});

// 3つ目のテスト
test('0と0の足し算は0になる', () => {
  expect(add(0, 0)).toBe(0);
});

// 三角測量によって導き出された実装
function add(a: number, b: number): number {
  return a + b;
}

どの手法をいつ使うべきか?

判断軸明確な実装仮実装三角測量
問題の複雑さ低い低〜中中〜高
実装の見通しはっきりしている一部見えているあいまいで未知が多い
テストの数少数で済む1つで様子を見る増やして一般化する
TDDの段階初期または明確な部分初期または不確実なとき実装を進化させるとき

三角測量における注意点

  • テストケースを追加するときは、「境界値」「異常系」など、意味のある観測点を意識するとよい。
  • 不要な冗長テストにならないよう注意(実装が一般化された後は、類似のテストは削除してよい)。
  • 2つ目のテストケースで実装が不十分と判断したら、すぐに3つ目を追加して一般化を強化する。

アンチパターンと注意点

アンチパターン説明対策
「最初から完璧な実装」を目指すRed→Greenのプロセスを無視して一気に実装してしまう必ずRed→Green→Refactorを守る
仮実装から脱却できないべた書きが残り、本来の実装に進化できない新しいテストで一般化を促す
三角測量のテストが増えすぎる類似ケースを無意味に追加してしまう意味の異なる値を使い、過剰なテストは整理する

Released under the CC-BY-4.0 license.