TDD とは何で、何ではないか: テストを書くこととの違い
同じ機能に対するテストでも、実装の前に書いたか後に書いたかで中身が変わります。書いた人の技量ではなく、書いた時点で何が見えているかが違うからです。
後から書くとき、目の前には完成したコードがあります。条件分岐も、早期リターンも、境界の値も、すべて読める状態です。このとき自然に出てくるのは「この分岐を通すテスト」です。
先に書くとき、目の前にはまだ何もありません。あるのは「何ができていれば正しいか」という問いだけです。このとき出てくるのは「この入力ならこうなってほしい」という要求です。
この章は、その差がどこから来るのかを見ます。
この章で学ぶこと
- テストを書く時点の違いが、テストの内容をどう変えるか
- TDD と呼ばれない活動との境目
- BDD や ATDD という近い名前のものとの関係
- このガイドが扱う TDD の範囲
どちらかの言語でテストを 1 本でも書いて動かしたことがあれば読めます。フレームワークは問いません。
この章で扱わないこと
| 観点 | 参照先 |
|---|---|
| テストを書くこと自体の価値、回帰の検出 | Pest でテストを書く |
| どの層にどれだけテストを書くかの配分 | テスト戦略 |
| テストフレームワークの選び方と設定 | TypeScript 開発ツールチェーン |
同じ機能に、違うテストが付く
配送料を返す関数を例にします。仕様は「5000 円以上なら無料、それ未満は 500 円」です。
実装を先に書いたとします。
function shippingFee(int $subtotal): int
{
if ($subtotal >= 5000) {
return 0;
}
return 500;
}
このコードを見ながらテストを書くと、多くの場合こうなります。
test('配送料を計算できる', function () {
expect(shippingFee(8000))->toBe(0);
expect(shippingFee(3000))->toBe(500);
});
分岐は 2 つあり、テストも 2 つ通しています。カバレッジは 100 パーセントです。
一方、実装がまだ無い状態で書くと、手が止まる箇所があります。「5000 円以上」と言われたとき、5000 円ちょうどはどちらなのかを決めないとテストが書けません。仕様を読み直すか、決めた人に聞くことになります。
test('5000 円ちょうどは無料になる', function () {
expect(shippingFee(5000))->toBe(0);
});
test('4999 円には配送料がかかる', function () {
expect(shippingFee(4999))->toBe(500);
});
TypeScript でも同じことが起きます。
// 実装を見てから書いたテスト
it('配送料を計算できる', () => {
expect(shippingFee(8000)).toBe(0)
expect(shippingFee(3000)).toBe(500)
})
// 実装より先に書いたテスト
it('5000 円ちょうどは無料になる', () => {
expect(shippingFee(5000)).toBe(0)
})
it('4999 円には配送料がかかる', () => {
expect(shippingFee(4999)).toBe(500)
})
何が違いを生むのか
2 つ目の書き方が優れているのは、テストの本数が多いからではありません。境界の値を、実装を決める前に問うているからです。
実装を見てからテストを書くと、>= と > のどちらが正しいかを問う機会がありません。コードに書いてある方が正解として目に入るからです。仕様の取り違えがあっても、テストはその取り違えごと固定します。
これが、書く順序を変えると出てくるものが変わる理由です。テストが仕様の確認になるか、実装の追認になるかが分かれます。
テスト名にも差が出ます。実装をなぞったテストは「計算できる」のように、何を確かめているのか読み取れない名前になりがちです。要求から書いたテストは、名前がそのまま仕様の 1 行になります。
TDD と呼ばれないもの
いくつかの活動は TDD と混同されますが、別のものです。
カバレッジを上げることは TDD ではありません。カバレッジは書いたテストが到達した範囲を測る指標で、書いた順序を測りません。上の 1 つ目の例はカバレッジ 100 パーセントですが、境界は確かめていません。
テストを後から充実させることも TDD ではありません。これは価値のある活動ですが、設計に影響を与える機会は過ぎています。コードの形は既に決まっているからです。
テストを大量に書くことも TDD ではありません。本数は結果であって目的ではありません。
近い名前のもの
TDD の周辺には、似た略語がいくつかあります。
| 呼び名 | 何を先に書くか | 主な読み手 |
|---|---|---|
| TDD | 開発者が読むテスト | 書いた本人と、後からコードを触る人 |
| BDD | 振る舞いを説明する文 | 開発者と、仕様を決める人 |
| ATDD | 受け入れ条件 | 開発者と、依頼した人 |
3 つは対立する手法ではありません。書く対象の粒度と、誰と共有するかが違います。BDD の道具立てで書かれたテストを内側で回せば TDD になりますし、ATDD の受け入れ条件を起点に内側を TDD で埋めることもできます。
このガイドは 3 つの区別を詳しく扱いません。開発者が自分のために書くテストを対象にします。
このガイドでの定義
本ガイドでは TDD を次の意味で使います。
実装する前に、その実装が満たすべきことをテストとして書き、失敗を確認してから実装する進め方。
短い定義ですが、含みが 3 つあります。
- 実装する前に: 順序が本体です。後から書いたテストは、どれだけ良くても別の活動です
- 満たすべきこと: 実装の手順ではなく、外から見た結果を書きます
- 失敗を確認してから: 書いたテストが本当に落ちることを目で見ます。理由は次の章で扱います
この定義には「どこまで細かく回すか」も「どの粒度のテストを書くか」も入っていません。そこは選択の余地があり、歩幅は 02 章、流儀の違いは 05 章で扱います。
まとめ
- 実装を見てから書くテストは、実装の分岐をなぞります。仕様の取り違えも一緒に固定します
- 実装より先に書くテストは、境界の値を決めることを要求します。テスト名が仕様の 1 行になります
- カバレッジを上げること、テストを後から足すこと、テストを大量に書くことは、いずれも TDD とは別の活動です
- BDD と ATDD は対立する手法ではなく、書く対象の粒度と共有する相手が違います
次に読む
- 02 Red-Green-Refactor — 「失敗を確認してから」の中身を、3 拍のサイクルとして扱います
- テスト戦略 — どの層にどれだけテストを書くかは、書く順序とは別の問いです
TDD の出発点は Kent Beck の『Test-Driven Development: By Example』(2002) です。本ガイドの定義も同書の流れを汲みますが、逐語の引用ではありません。