テストとコード品質

TDDテスト駆動開発、レッド・グリーン・リファクタサイクルの実態(実務体験)

「先にテストを書く? まだコードもないのに、何をテストするんですか?」

読了 4 分
TDDテスト駆動開発、レッド・グリーン・リファクタサイクルの実態(実務体験)のカバー画像

「先にテストを書く? まだコードもないのに、何をテストするんですか?」

TDD(Test-Driven Development、テスト駆動開発)に初めて触れると、誰もがそう思います。順番が逆に感じられ、わざわざ作業を2倍にしているように思えるからです。

でも、数か月実務で使い続けると、考え方は少し変わります。

TDDは、「失敗するテスト(Red)→ 通すための最小限のコード(Green)→ 整理(Refactor)」という短いサイクルを繰り返す開発手法です。ポイントは、このサイクルを数秒から数分単位で素早く回すことです。

今日は、このレッド・グリーン・リファクタサイクルが実際にどう動くのか、順を追って見ていきます。


TDDのレッド・グリーン・リファクタ、3ステップまとめ ✍️

まずはサイクル全体を一目で整理します。

  1. Red:まだ存在しない機能のテストを先に書く。当然、失敗する。
  2. Green:テストを通すための最小限のコードだけを書く。きれいでなくてもよい。
  3. Refactor:テストが通る状態を保ちながらコードを整理する。

この3段階をひとまとまりにして、繰り返し続けるだけです。

この3つの段階をぐるぐる回すのがTDDのすべてです
この3つの段階をぐるぐる回すのがTDDのすべてです

大切なのは、各段階を大きくしすぎないことです。「ログイン機能全体」ではなく、「メールアドレスが空ならエラーを返す」程度まで細かく分けます。

1サイクルに30分かかるなら、それはTDDというより、テストを添えた通常の開発である可能性が高いです。


Redステージ、なぜわざと失敗するコードを書くのか?

TDDを学び始めたとき、最も理解しにくい部分です。失敗すると分かっているのに、なぜ実行するのでしょうか。

理由は単純です。そのテストが「正しく失敗するか」を確認するためです。

テストを書いた直後に通ってしまうなら、そのテストは何も検証していない空の殻かもしれません。先に失敗を見るのは、テスト自体をテストする工程なのです。

簡単な例として、2つの数を足す関数を作るとします。コードより先にテストが登場します。

// add 関数はまだ存在しない。そのため、このテストは失敗する(Red)
struct AddTests {
    @Test func 足し算_1加算2は3である() {
        #expect(add(1, 2) == 3)
    }
}

この状態で実行すると、「cannot find ‘add’ in scope」のようなエラーが出ます。私はむしろ、この赤信号を見ると安心します。目標が明確になるからです。

ここからの仕事はただ1つ、その赤信号を青信号に変えることです。

赤信号を見ると、次にやることが明確になります
赤信号を見ると、次にやることが明確になります

GreenとRefactor、ここで本当の実力が分かれる

Greenステージの原則は、「恥ずかしいほど単純に」です。

先ほどのテストを通す最速のコードはこれです。

// テストを通すための最小限のコード(Green)
func add(_ a: Int, _ b: Int) -> Int {
    return a + b
}

あまりに当然に見えますよね。でも初心者ほど、ここで将来を心配し、例外処理やログ、設定値まで先に入れようとします。

TDDは、その衝動を意図的に抑えさせます。今のテストが要求していないコードは書かない、というのがルールです。

そして、青信号が点いてから初めてRefactorに移ります。ここで変数名を整えたり、重複を取り除いたりします。

重要なのは、Refactor中は新しい機能を絶対に追加しないことです。テストが通る状態を守りながら、構造だけを改善します。

整理中に赤信号が点いたら? 直前に変更した部分を戻せばよいのです。少し前まで正常だったことを、テストが保証してくれます。

この安全網のおかげで、リファクタリングにもっと大胆に取り組めるようになりました。

赤信号を青信号に変えるこの感覚が、やみつきになります
赤信号を青信号に変えるこの感覚が、やみつきになります

TDDをすると開発は遅くなりませんか? 🤔

最もよく受ける質問です。実際に使ってみた答えは、「場合によります」でした。

私が感じた違いを表にまとめました。

項目 TDDを適用 後からテストを書く
序盤の速度 やや遅い 速い
バグの発見時期 実装直後 かなり後
リファクタリングの負担 低い 高い
複雑なロジック 有利 不利

単純な画面を1つ手早く作るときは、正直TDDが煩わしく感じることもあります。

一方、決済や精算、割引計算のように条件が絡み合うロジックでは、確かな効果を発揮しました。ケースを1つずつテストで固めておくと、後からコードを大きく変更しても怖くありません。

結局、TDDは万能薬ではなく、「複雑さを扱うための道具」に近いものでした。


まとめ

TDDが難しく感じるなら、壮大に始めず、まずは1つの関数でレッド・グリーン・リファクタを回してみてください。

小さな成功サイクルが身についた瞬間、なぜ人々がこの手法を手放せないのか、少し分かるはずです。今日はまず1つのテストで赤信号を点けてみてください。

あわせて読みたい