概要
TDD(Test-Driven Development)では、
まず「失敗するテスト」を書くことからすべてが始まる。
それは単なるルールではなく、設計を「防御的かつ先回り的」に進める知的戦略である。
本稿では、Go言語を使った実装例を通して、
「TDDにおける失敗するテスト」の意義、構造、設計的価値を深掘りする。
1. なぜ「失敗するテスト」を先に書くのか?
✅ 仕様が不明確なまま書かれたコードは、防御不能になる
- 期待を明文化せずにコードを書くと、バグではなく“未定義”が量産される
- TDDは「何が期待されるか」をコードよりも先に言語化するため、設計が意識的になる
✔️ Red(失敗)とは、仕様未満であることを意味する警報である。
2. 実践例:Goで負の数を扱えない平方根関数をTDDで拡張
初期仕様(正の数のみ)
// sqrt.go
package sqrt
import "math"
func Sqrt(n float64) float64 {
return math.Sqrt(n)
}
// sqrt_test.go
package sqrt
import "testing"
func TestSqrt_PositiveNumber(t *testing.T) {
got := Sqrt(9)
want := 3.0
if got != want {
t.Errorf("got %v, want %v", got, want)
}
}
失敗するテストを書く(Red)
func TestSqrt_NegativeNumber(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("expected panic on negative input")
}
}()
Sqrt(-1)
}
実行結果:
--- FAIL: TestSqrt_NegativeNumber
sqrt_test.go:...: expected panic on negative input
3. テストから見えてくる「設計の空白」
- 現時点の仕様では、負の数に対する振る舞いが未定義(math.SqrtはNaNを返す)
- TDDは「仕様の抜け漏れ」をRedであぶり出す
- ここで**「何を期待するか」=「何をガードすべきか」**を定義する
4. Green – ガードを導入して通る実装に
func Sqrt(n float64) float64 {
if n < 0 {
panic("negative input not allowed")
}
return math.Sqrt(n)
}
テスト結果:
✓ TestSqrt_PositiveNumber
✓ TestSqrt_NegativeNumber
5. Redは「バグ検知」ではなく「仕様強化」
Redを書かなければ見逃されていたかもしれないもの
不具合の種 | 設計的ガード |
---|---|
nullや0除算 | 明示的に制御すべき入力 |
境界値 | テストを書くことで意識が向く |
不正状態 | Redを先に書くことで構造が強化される |
設計判断フロー
① この仕様に対して、失敗してほしいパターンはあるか? → YES → Redを書く
② 書いたRedが、仕様として意味を持つか? → YES → 実装に意味を与える
③ Redが通った後、実装に過剰なものが残っていないか? → NO → Greenを最小化
④ 想定外の入力は、定義しておくべきか、黙殺するか? → YES → テストで明示
よくあるミスと対策
❌ 正常系しかテストを書かない
→ ✅ 仕様が単線化し、想定外の挙動を許容する
→ 🔁 Redで失敗系・境界系・例外系を先に書くことで、設計に幅が生まれる
❌ Redを「ただ通すためだけ」の作業になる
→ ✅ Redは「何を恐れているか」を意図として書く。思想を持った失敗を。
結語
TDDにおける「Red」は、単なる失敗ではない。
それは**“まだ満たされていない仕様がある”というシグナルであり、設計を先鋭化させる対話の起点”**である。
- 仕様の「抜け」は、Redによって炙り出される
- 実装の「過剰」は、Greenによって抑制される
- 構造の「曖昧さ」は、Refactorによって洗練される
TDDとは、
“仕様の不完全さをRedで照らし、進化するシステムの防壁を構築する戦略である。”