仕様書をそのままテストケースで書く:品質の出口はテスト
概要¶
「品質の出口はテスト。なら仕様書からテストケースで書く」という考え方。仕様書のIF文・条件・期待値をそのままテストコードに落とし込むことで、仕様とテストのズレをなくす。
詳細¶
従来のアプローチの問題¶
仕様書ベースのテスト設計¶
仕様書の条件文をそのままテストに変換する。
仕様書の例:
注文API:
- 在庫が足りる場合: 注文を確定し、在庫を減らす
- 在庫が足りない場合: エラーを返す("insufficient stock")
- ユーザーが未認証の場合: 401を返す
- 金額が0以下の場合: バリデーションエラーを返す
テストコードへの変換(Go):
func TestOrderAPI(t *testing.T) {
tests := []struct {
name string
stock int
quantity int
authenticated bool
amount float64
wantStatus int
wantError string
}{
{
name: "在庫が足りる場合: 注文を確定する",
stock: 10,
quantity: 5,
authenticated: true,
amount: 1000,
wantStatus: 200,
},
{
name: "在庫が足りない場合: エラーを返す",
stock: 3,
quantity: 5,
authenticated: true,
amount: 1000,
wantStatus: 400,
wantError: "insufficient stock",
},
{
name: "未認証の場合: 401を返す",
authenticated: false,
wantStatus: 401,
},
{
name: "金額が0以下: バリデーションエラー",
authenticated: true,
amount: 0,
wantStatus: 422,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// テスト実行
})
}
}
ポイント: テスト名が仕様書の条件文と1対1で対応している。
仕様変更時の追跡¶
仕様変更: 「在庫が3個以下の場合は警告を出す」が追加
→ テストに新しいケースを追加するだけ:
{
name: "在庫が3個以下: 警告フラグをセットする",
stock: 3,
wantStatus: 200,
wantWarning: true,
},
Given-When-Then パターン¶
// Given: 前提条件
// When: 操作
// Then: 期待結果
func TestOrder_GivenSufficientStock_WhenOrderPlaced_ThenStockDecreased(t *testing.T) {
// Given
stock := NewStock(10)
order := NewOrder(quantity: 3)
// When
err := order.Place(stock)
// Then
assert.NoError(t, err)
assert.Equal(t, 7, stock.Remaining())
}
なぜ重要か / いつ使うか¶
- 「テストが通っているのに本番でバグる」を防ぐ(仕様とテストが一致していなかった)
- 仕様書の品質も向上する(テストに書けない仕様は曖昧な仕様)
- チームで仕様書とテストを一緒にレビューできる
- リグレッションテストの網羅性を高めたいとき