Skip to main content

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) です。本ガイドの定義も同書の流れを汲みますが、逐語の引用ではありません。