行為驅動開發(BDD, Behaviour-Driven Development)與實例化規格(Specification by Example)關注的是測試金字塔最上層的「驗收層」:不是「程式碼寫對了嗎」,而是「我們有沒有做對的東西」。它們把需求、測試、文件三者合而為一,讓業務、開發、測試講同一種語言。

本章聚焦驗收層的協作與規格;單元層的 given/when/then 與 Java 實作見 Java 單元測試深度章節。兩者分層互補——詳見文末「與單元層的分層關係」。

BDD 的核心價值:不只是自動化#

最常見的誤解,是把 BDD 當成「一種自動化測試工具(Cucumber)」。自動化只是它的副產品,真正的價值在前面:

BDD 的核心,是用一套業務可讀、結構固定的語言,逼著團隊在動工前把「完成的定義」談清楚。一份 Given-When-Then 同時是:需求(業務看得懂)、測試(機器可執行)、文件(永遠跟程式碼同步)。三合一,消除了三份文件互相對不上的老問題。

flowchart LR
    A[需求 / 期望行為] --> B[用具體實例闡明]
    B --> C[Given-When-Then 規格]
    C --> D[自動化驗收]
    D --> E[活文件<br/>Living Documentation]
    E -.持續回饋.-> A
BDD 帶來的轉變說明
需求三合一同一份規格既是需求、又是測試、又是文件
對話優先價值多半在「寫規格前那場對話」,而非規格檔本身
由外而內從使用者可見的行為往內驅動開發,避免做出沒人要的功能
文件不腐爛規格由自動化執行把關,過時了就會紅燈,不會悄悄失真

Gherkin 慣例:Given-When-Then#

Gherkin 是描述行為的結構化語言,核心關鍵字三個:

  • Given:前置情境(系統已處於什麼狀態)
  • When:觸發事件(使用者/系統做了一個動作)
  • Then:預期結果(應觀察到什麼)
Feature: 購物車金額計算
  作為顧客
  我希望結帳金額正確反映優惠
  以便信任這個系統

  Scenario: 一般顧客滿千未享免運
    Given 我是一般會員
    And 購物車金額為 800    When 我前往結帳
    Then 運費應為 60
  Scenario Outline: 依會員等級計算運費
    Given 我是 <會員等級> 會員
    And 購物車金額為 <金額>    When 我前往結帳
    Then 運費應為 <運費>
    Examples:
      | 會員等級 | 金額 | 運費 |
      | 一般     | 800  | 60   |
      | 一般     | 1200 | 0    |
      | VIP      | 100  | 0    |
結構用途
Feature一個功能模組,含「角色—期望—價值」的敘事
Scenario單一具體情境
Scenario Outline同一情境結構、多組資料,用 Examples 表展開(資料驅動)
Background多個 Scenario 共用的前置 Given(避免重複)

Gherkin 反模式:命令式 vs 宣告式#

寫 Gherkin 最大的陷阱,是把它當成「點按操作腳本」來寫(命令式),而不是「描述業務意圖」(宣告式)。命令式 scenario 又長又脆——UI 一改全爆,而且讀者看完仍不知道這條規則在講什麼。

# ✗ 命令式(imperative):描述「怎麼操作」,混入 UI 細節
Scenario: 結帳免運
  Given 我打開瀏覽器前往首頁
  And 我點擊「登入」按鈕
  And 我在帳號欄輸入 "vip01"
  And 我在密碼欄輸入 "pw"
  And 我點擊「送出」
  And 我搜尋商品 "滑鼠"
  And 我點擊「加入購物車」
  When 我點擊「前往結帳」
  Then 我看到運費欄顯示 "0"
# ✓ 宣告式(declarative):描述「業務意圖」,與 UI 解耦
Scenario: VIP 顧客享免運
  Given 我是 VIP 會員
  And 購物車內有商品
  When 我前往結帳
  Then 運費應為 0

判斷標準:把網站換成 App、把按鈕換成語音指令,這條 scenario 還成立嗎? 宣告式的會;命令式的會整條報廢。操作細節屬於步驟定義(step definition)那層的實作,不該出現在規格裡。

實例化規格的七個實踐#

實例化規格是一套讓「規格」從需求一路活到生產的協作流程,可歸納為七個環環相扣的實踐:

#實踐重點
1從目標推導範圍先問業務目標,由目標反推該做哪些功能,而非照單全收需求
2協作制定規格業務、開發、測試一起定義規格,而非單方丟需求文件
3用實例闡明用具體例子(含邊界、例外)說清楚規則,消除模糊地帶
4精煉規格去掉操作雜訊與重複,留下宣告式、聚焦業務意圖的關鍵實例
5自動化驗證把精煉後的規格接上自動化,使其可重複執行
6頻繁驗證每次變更都跑規格,確保系統與規格始終一致
7演進出活文件規格隨系統持續更新,沉澱為可信賴的活文件
flowchart TD
    G[1. 從目標推導範圍] --> C[2. 協作制定規格]
    C --> I[3. 用實例闡明]
    I --> R[4. 精煉規格]
    R --> A[5. 自動化驗證]
    A --> F[6. 頻繁驗證]
    F --> L[7. 演進出活文件]
    L -.下一輪迭代.-> G

活文件(Living Documentation)#

活文件是上述流程的最終產物:一份會被自動化持續驗證、因而永遠與系統行為一致的說明文件。

傳統文件活文件
寫完就開始腐爛、與程式碼脫節過時就紅燈,被迫保持同步
沒人信任、沒人讀是唯一可信的行為來源(single source of truth)
維護是額外負擔維護即開發流程本身,無額外成本

活文件讓「文件」從成本變成資產:新成員讀規格就能理解系統行為,業務看規格就能確認需求,而你完全不必擔心它在說謊——因為說謊的那一刻它就會讓建置失敗。

貫穿例子:VIP 訂單免運費#

以下示範一條業務規則如何走完「規格 ➡️ Gherkin ➡️ 驗收」。

1. 規格(協作後用實例闡明的規則)

VIP 會員一律免運費;一般會員滿 1000 元免運,未滿收 60 元。

2. Gherkin(精煉成宣告式、含邊界實例)

Feature: 訂單運費規則

  Scenario Outline: 依會員等級與金額計算運費
    Given 我是 <等級> 會員
    And 購物車金額為 <金額>    When 我前往結帳
    Then 運費應為 <運費>
    Examples:
      | 等級 | 金額 | 運費 |
      | VIP  | 100  | 0    |
      | 一般 | 999  | 60   |
      | 一般 | 1000 | 0    |

3. 驗收(步驟定義把規格綁到系統)

public class ShippingFeeSteps {

    private Cart cart;
    private int actualFee;

    @Given("我是 {word} 會員")
    public void setMembership(String level) {
        cart = new Cart(MemberLevel.from(level));
    }

    @Given("購物車金額為 {int} 元")
    public void setAmount(int amount) {
        cart.setSubtotal(amount);
    }

    @When("我前往結帳")
    public void checkout() {
        actualFee = new ShippingPolicy().calcFee(cart);
    }

    @Then("運費應為 {int} 元")
    public void verifyFee(int expected) {
        assertEquals(expected, actualFee);
    }
}

注意 9991000 兩個 Examples——它們把「滿 1000」這條規則的邊界值直接寫進了活文件。任何人讀規格都能立刻看出臨界點在哪,這正是「用實例闡明」消除模糊的威力。

與單元層的分層關係#

驗收層的 Gherkin 與單元層的 given/when/then 形式相近,但目的與粒度不同,兩者協同覆蓋不同風險。

維度驗收層(本章 Gherkin)單元層(07 Java 章
回答的問題有沒有做對的東西(外部行為)程式碼有沒有寫對(內部邏輯)
語言業務可讀的自然語言開發者導向的測試程式碼
粒度端到端的業務情境單一函式/類別的行為
速度/數量較慢、數量少(金字塔頂)極快、數量多(金字塔底)
讀者業務 + 開發 + 測試開發者
flowchart TD
    A[驗收層: Gherkin 業務情境<br/>確認做對的東西] --> B[單元層: given/when/then<br/>確認程式碼寫對]
    A -.少量, 慢.-> A
    B -.大量, 快.-> B

別把所有規則都推到驗收層測——那會得到一堆又慢又脆的端到端測試。驗收層只覆蓋關鍵業務情境(活文件價值最高處),細節邏輯與邊界組合下沉到單元層,符合測試金字塔。