認識 Test-Driven Development (TDD)

自從踏入軟體開發職場後,我最早認識到的開發流程之一就是 TDD,我也認為他應該是每位軟體開發者都應該知道的開發流程,即使在實務上並非所有專案或情境都適合採用,但我覺得能有 TDD 的概念也很棒。

TDD 的核心價值並不只是確保功能能正常運作,而是透過「先寫測試」的方式,引導開發者思考程式介面的使用方式,進而提升程式碼的可測試性與可維護性。

不過,TDD 並不是一套完整的軟體設計方法,它無法直接保證系統架構的最佳化,但我認為能具備 TDD 的思維,本身就是建立良好工程習慣的重要一步。

Test-Driven Development (TDD)

TDD 全名是 Test-Driven Development,也就是「測試驅動開發」,是一種以測試驅動開發的開發流程。

在傳統開發中,我們通常會先把功能寫出來,最後再補上測試,或是靠手動操作確認功能是否正常。然而在實際專案裡,這種方式常常會遇到一些狀況:明明 A 功能沒動,修改 B 之後 A 就突然壞掉,或功能在自己環境看起來都正常,一到測試階段 QA 那邊就冒出一些莫名其妙的 bug。

TDD 的觀念剛好反過來。它強調 「先寫測試,再進入開發」。第一次聽到時,很多人都會覺得不可行:「功能都忙不完了,哪有空先寫測試?」但如果換個角度思考,其實 TDD 的流程跟 QA 在專案初期會先撰寫 Test Case 的概念非常接近:

  • 先明確定義功能應該具備的行為與邊界
  • 再依照這些行為規格實作程式碼
  • 讓每一行新程式碼都有對應的驗證依據
  • 在修改或擴充功能時,能即時發現是否破壞既有行為

TDD 做的事情,就是把這套流程提前到工程師手上,讓開發在寫第一行程式碼之前,就先釐清:「這個功能到底要做什麼?」、「哪些情境應該通過?」、「哪些行為應該失敗?」。

因此,先寫測試並不是單純在完成測試本身,而是在迫使開發者先釐清介面與行為的預期,再著手撰寫程式碼。這種方式自然也讓程式碼設計更清晰,也避免過早陷入實作細節。

TDD 精神

不過 TDD 的核心精神並不是寫測試本身,而是透過測試來驅動設計、驗證行為、降低開發風險。

其中包含三個重要精神:

  1. 以「行為」為中心的開發思維(Behavior-Oriented Thinking)

    在 TDD 中,測試案例不是用來檢查程式,而是用來描述功能應該如何運作(specification)。

    • 測試等於需求的具體描述
    • 測試寫的不是「怎麼做」,而是「應該發生什麼事」
    • 讓開發者先思考 使用者角度 的行為,而不是陷入實作細節
  2. 反覆的小步前進(Small Steps, Fast Feedback) TDD 最核心的原則之一就是「Small Steps」

    • 每一次只加入一點點新行為
    • 每一次只寫一個失敗測試
    • 每一次只寫最小的程式碼讓它通過

    這麼做是為了讓開發過程保持可控、可預測、可追蹤,以及回饋,讓你可以快速發現問題,而不是等到最後才發現整包炸掉。

  3. 透過測試確保設計的可維護性(Design for Testability) TDD 的過程會透過測試讓程式碼呈現良好的設計特色,像是:

    • 模組化
    • 小型、可重複使用的函式
    • 低耦合、高內聚 而這些都不是你刻意做的,是因為『你沒辦法寫出難測試的程式碼,而能被測試的程式碼自然就設計得比較好。』

開發流程

接下來進入主軸,TDD 最廣為人知的流程就是 Red → Green → Refactor,也有人稱它為「紅綠重構循環」。

這並不是一個一次跑完的流程,而是一個會在開發過程中不斷重複的小循環。

這個循環的重點不在於「寫很多測試」,而在於用測試來限制與引導程式碼的成長方向。

Red:先讓測試失敗

第一步永遠是:先寫一個會失敗的測試。

這個測試通常描述的是「一個尚未存在的行為」:

例如在開發一個下單流程時,Red 階段通常會明確寫出像這樣的測試情境:

  • 購物車為空時,不該前往結帳,應該拋出錯誤
  • 商品庫存不足時,不應該產生訂單
  • 成功下單時,應該回傳訂單編號

在這個階段,你不需要關心實作方式,只需要專注在應該表現出什麼行為

Green:用最小成本讓測試通過

接下來這個目標很單純,就是讓剛剛那個失敗的測試變成通過(Green)。

這裡有一個 TDD 初期最容易有誤解的地方:

Green 階段 不追求漂亮的設計,也不追求完整性

因此在這個階段:

  • hardcode 是合理的
  • 邏輯重複是可接受的
  • 結構不好看是預期中的狀態

在實務專案裡,這樣做可以避免一個常見問題:

為了「可能會用到」,提前設計過度彈性的結構。

Green 階段實際帶來的好處是:

  • 功能只成長到「目前需要的程度」
  • 每次 commit 的變動範圍很小
  • 問題一出現,幾乎可以立刻定位

如果測試是綠的,就代表在目前定義的規格下,這個行為是成立的。

Refactor:當行為能夠被測試,才開始談設計

當測試全綠之後,才進入 Refactor 階段。

因為到了這個時間點,功能的行為基本已經被測試固定住,所以你可以安心重構,而不用擔心改壞功能。

在這個階段通常會做:

  • 抽出重複邏輯
  • 拆分過於肥大的 function / class,保持 SRP
  • 重新命名,讓意圖更清楚
  • 調整模組邊界,降低耦合

如果你發現「不敢重構、改不動」,通常代表測試寫得不夠貼近行為,或測試層級不對。

重複循環

在真實專案中,需求幾乎不可能一次定義完成。

完成一次 Red → Green → Refactor 後,你不會停下來。

TDD 的循環設計,讓你可以用非常低的成本去回答:

  • 新需求加進來,會不會影響舊行為?
  • 目前的設計能夠支援嗎?
  • 是時候重構,還是先忍一下?

每一次 Red → Green → Refactor,都只處理一個小問題,讓系統以「可回頭」的方式前進。

久了之後你就會發現:

  • 程式碼不是一次設計完成,而是隨著需求在多次迭代中逐步被推導出來
  • 重構變成一種日常行為,而不是等到累積成大型風險才一次處理
  • 測試成為系統最可信、可隨迭代持續更新的行為文件

Licensed under CC BY-SA 4.0

Unless otherwise noted, content on this site is licensed under CC BY-SA 4.0. You are free to share and adapt with attribution.

關於 Domain-Driven Design (DDD)

自數位轉型年代開始,軟體開發領域開始流行 DDD 一詞的說法,所謂領域驅動設計 Domain-Driven Design (DDD) 是一種軟體設計方法論,核心目的是讓程式設計與商業邏輯緊密對齊,以便未來因應商業需求變更。

CA: Data Transfer Object (DTO)

為了解決層與層之間的耦合,內部核心 Entities、Use Cases 去依賴外部格式,可能會造成今天欄位異動,連動核心需要跟著去修改,違反了依賴反轉原則(DIP),日後要擴充或是重構也是一大挑戰。