
TDD / BDD / SDD:三種驅動開發的思維模式
重點不是「先寫測試」,而是「用什麼來定義正確」。
為什麼要談這個
很多人把 TDD 理解為「先寫測試再寫功能」,然後覺得太慢、太麻煩、不實際。
這是因為他們只看到了手法,沒看到背後的哲學。
TDD / BDD / SDD 本質上在回答同一個問題:「你怎麼知道你寫的東西是對的?」
三者的差別在於:「誰來定義什麼叫對?」
三種「驅動」的核心差異
graph TD A[一個功能需求] --> B{由誰定義正確?} B -->|開發者視角| C[TDD<br/>用程式碼的行為定義] B -->|使用者視角| D[BDD<br/>用商業場景定義] B -->|合約視角| E[SDD<br/>用規格文件定義] style C fill:#4CAF50,color:#fff style D fill:#2196F3,color:#fff style E fill:#FF9800,color:#fff
| TDD | BDD | SDD | |
|---|---|---|---|
| 驅動力 | 測試案例 | 行為場景 | 規格文件 |
| 語言 | 程式碼(assert) | 自然語言(Given-When-Then) | 結構化文件(OpenAPI, Proto) |
| 誰最該看懂 | 開發者 | PM / QA / 開發者 | 所有利害關係人 |
| 定義「正確」的基準 | 所有測試通過 | 所有場景覆蓋 | 實作符合規格 |
| 最大價值 | 強迫你想清楚邊界條件 | 讓技術與商業對齊 | 先定義合約再分頭實作 |
TDD — 用失敗的測試來思考
核心循環:Red → Green → Refactor
1. Red — 寫一個會失敗的測試(因為功能還沒實作)
2. Green — 用最簡單的方式讓測試通過
3. Refactor — 在測試保護下重構,讓 code 變乾淨
TDD 真正在教你的事:
- 在寫功能之前,先想清楚這個功能的邊界是什麼。 輸入是什麼?輸出是什麼?null 怎麼處理?空陣列呢?
- 「最簡單的方式讓測試通過」不是偷懶,是刻意的。 它逼你一次只解決一個問題,避免過度設計。
- 測試不是事後補的安全網,而是設計工具。 如果你發現測試很難寫,通常代表你的 API 設計有問題。
什麼時候 TDD 特別有用:
- 演算法邏輯、資料轉換、工具函式
- 你很清楚輸入輸出,但邊界條件很多的場景
什麼時候 TDD 不太適合:
- UI 開發(行為難以用 assert 定義)
- 探索性開發(你還不知道要什麼)
BDD — 用場景來對齊期望
核心格式:Given / When / Then
Feature: 使用者登入
Scenario: 正確帳密登入
Given 使用者已註冊,帳號 "terry@example.com"
When 使用者用正確密碼登入
Then 應該看到首頁
And 導覽列顯示使用者名稱
Scenario: 密碼錯誤
Given 使用者已註冊
When 使用者用錯誤密碼登入
Then 應該看到「帳號或密碼錯誤」的提示
And 不應該被導向首頁BDD 真正在教你的事:
- 需求不是模糊的一句話,而是一組可驗證的場景。 當 PM 說「要有登入功能」,BDD 逼你問:「登入失敗要顯示什麼?連續失敗三次呢?」
- 用人話寫測試,所有人都能參與 review。 QA 可以補場景、PM 可以確認這是不是他要的行為。
- 場景就是活的文件。 它永遠跟程式碼同步,不像 spec 文件寫完就過時。
什麼時候 BDD 特別有用:
- 功能型需求(使用者登入、購物車結帳、權限控制)
- 跨角色溝通成本高的團隊
常見工具: Cucumber、Jest + Gherkin、Behave (Python)
SDD — 用規格合約來定義邊界
核心概念:先定義介面,再分頭實作
# OpenAPI 規格範例
paths:
/users/{id}:
get:
summary: 取得使用者資料
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: 成功
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: 使用者不存在SDD 真正在教你的事:
- 合約是團隊協作的基礎。 前後端可以同時開工,因為介面已經定義好了。
- 規格即文件、即測試、即 mock。 從 OpenAPI 可以自動生成 mock server、client SDK、API 文件。
- 強迫你在寫 code 之前就想清楚 API 的形狀。 欄位名稱、型別、必填與否、錯誤碼——這些都在寫第一行程式碼之前就定義好。
什麼時候 SDD 特別有用:
- 前後端分離的團隊
- 微服務之間的溝通(gRPC / Protocol Buffers)
- 公開 API(你不能隨便改介面)
三者不互斥,而是不同層次
一個成熟的專案可能同時使用:
SDD — 定義 API 合約(團隊對齊用)
↓
BDD — 定義功能場景(需求驗證用)
↓
TDD — 定義單元行為(實作品質用)
就像蓋房子:SDD 是藍圖、BDD 是「住進去之後什麼體驗」、TDD 是「每根柱子的承重夠不夠」。
你該怎麼選?
不要問「我該用 TDD 還是 BDD」,而是問:
| 你現在的痛點 | 適合的方向 |
|---|---|
| 寫完功能才發現邊界條件沒處理 | TDD — 先想邊界再寫功能 |
| PM 驗收時說「這不是我要的」 | BDD — 用場景對齊期望 |
| 前後端一直在等對方 | SDD — 先定義合約再分頭做 |
| 重構很怕改壞東西 | TDD — 測試就是安全網 |
| 文件永遠跟程式碼不同步 | SDD — 規格就是文件 |
反思問題
- 你現在的專案,「正確」是由什麼定義的? 是 PM 驗收時的口頭確認?還是有明確的場景或測試?
- 你上次因為「邊界條件沒想到」而出 bug 是什麼時候? TDD 的 Red 階段就是在逼你想這些。
- 你的 API 文件跟實際行為一致嗎? 如果不確定,SDD 可能是你需要的。
- AI 可以幫你生成測試、場景、規格,但你知道該生成什麼嗎? 這才是你要掌握的。
延伸閱讀
- 測試策略 — 測試金字塔與各層次的實務
- API 設計與認證機制 — SDD 的實際應用場景
- 系統分析與設計 — 從需求到規格的完整流程