---
# System prepended metadata

title: BDD 知識點分享
tags: [軟工, 分享]

---

# 關於 BDD 與 Gherkin 
> Behavior-Driven Development，行為驅動開發

BDD 是一種軟體開發方法論，從 TDD（測試驅動開發）演進而來。它強調透過「行為」來描述系統需求，關注軟體應該「做什麼」，而不只是「怎麼做」。

**核心理念：**
- 以行為為中心，從用戶和業務需求的角度描述系統行為
- 讓 PM、RD、QA 使用同一種語言溝通
- 將需求直接轉換為可執行的測試

BDD 解決了傳統開發中 **「 需求翻譯 」** 的問題，讓所有角色對需求有一致的理解。

## 什麼是 Gherkin？
Gherkin 是實現 BDD 的一種具體的規格語言，可以這樣理解：
- BDD = 開發方法論（ 概念層面 ）
- Gherkin = 描述語言（ 實踐工具 ）

### Gherkin 的特性
1. 領域特定語言（DSL）
-- 使用自然語言，但有明確的語法規則
-- 人類可讀，機器可執行
2. 結構化語法

```gherkin=
Feature: 功能描述
  Rule: 業務規則
    Scenario/Example: 測試場景
      Given: 給定（前置條件）
      When: 當（執行操作）
      Then: 那麼（預期結果）
      And: 和（連接同類步驟）
```

3. 可執行性
-- Gherkin 文件可被 Cucumber、SpecFlow、Behave 等工具解析
-- 連接到實際的測試代碼（Step Definitions）

:::info
Gherkin 核心關鍵字
| 關鍵字 | 用途 | 說明 |
|--------|------|------|
| **Feature** | 功能特性 | 描述要實現的功能模組 |
| **Rule** | 業務規則 | 原子化的業務邏輯單元 |
| **Scenario** | 具體場景 | 描述單一測試情境 |
| **Scenario Outline** | 參數化場景 | 用表格覆蓋多組測資 |
| **Given** | 前置條件 | 設定初始狀態 |
| **When** | 執行動作 | 觸發的事件或操作 |
| **Then** | 預期結果 | 驗證的輸出 |
| **And / But** | 連接步驟 | 連接多個同類步驟 |
| **Background** | 共用前置 | 所有場景共享的前置條件 |
| **Examples** | 測試數據表 | 提供多組參數化數據 |
:::

---

## Gherkin 撰寫實務範例

### **▶ 基礎層：Feature + Scenario（正例 + 反例）**
> **適用時機：** 驗證單一功能的基本行為

```gherkin=
Feature: 查詢商品價格
  使用者可以查詢系統中任一商品的當前價格
  
  Scenario: 查詢已上架商品的價格（正例）
    Given 商品 "可樂" 存在於系統中，價格為 30 元
    When 使用者查詢商品 "可樂" 的價格
    Then 系統回傳價格為 30 元
  
  Scenario: 查詢不存在的商品（反例）
    When 使用者查詢商品「不存在的商品」的價格
    Then 操作失敗
```

**撰寫要點：**
- 使用雙引號「 `""` 」標記參數
- 數字可以直接使用，無需引號
- 反例的 Given 可以省略（無前置條件時）
- 應包含正例與反例

<br />

---

### **▶ 中階層：Feature + Rule + Scenario（原子化規則）**
> 適用時機：功能包含多條業務規則
> 
```gherkin=
Feature: 計算訂單折扣

  Rule: 滿額折價
    當單筆訂單金額達到門檻時，系統應給予相應折扣
    
    Example: 訂單金額達到折扣門檻
      Given 系統設定滿 1000 元折 100 元
      And 購物車中有商品總額為 1200 元
      When 使用者結帳
      Then 折扣金額為 100 元
      And 最終金額為 1100 元
    
    Example: 訂單金額未達折扣門檻
      Given 系統設定滿 1000 元折 100 元
      And 購物車中有商品總額為 800 元
      When 使用者結帳
      Then 折扣金額為 0 元
      And 最終金額為 800 元
```

**原子化原則：**
- 每條 Rule 是一個不可分割的業務邏輯單元
- 每個 Feature 對應開發中要實作的一個邏輯模組
- Rule 幫助拆解複雜功能為獨立規則

<br />

---

### **▶ 進階層：Feature + Rule + Scenario Outline + Examples**
> 適用時機：需要驗證多組測資和邊界條件
```gherkin=
Feature: 會員等級折扣計算
  
  Rule: 不同會員等級享有不同折扣率
    系統根據會員等級自動計算折扣價格
    
    Scenario Outline: 查詢商品價格並套用會員折扣
      Given 商品 "可樂" 原價為 30 元
      And 會員「<會員名稱>」的等級為「<會員等級>」
      When 會員「<會員名稱>」查詢商品 "可樂" 的價格
      Then 系統回傳折扣後價格為 <折扣價> 元
      
      Examples: 一般場景
        | 會員名稱 | 會員等級 | 折扣價 |
        | 小明     | 一般會員 | 30   |
        | 小華     | 銀卡會員 | 27   |
        | 小李     | 金卡會員 | 24   |
      
      Examples: 邊界場景
        | 會員名稱 | 會員等級 | 折扣價   | 備註             |
        | 小王     | 白金會員 | 21     | 最高折扣          |
        | 小陳     | 未登入   | 30     | 未登入無折扣      |
        | 小張     | 黑名單   | 錯誤    | 黑名單禁止查詢價格 |
```

**優勢：**
- 避免重複撰寫相似場景
- 清楚呈現不同輸入與輸出的關係
- 可分組測試數據（一般場景 vs 邊界場景）


<br />

## Gherkin 進階技巧
**▶ Background：提煉共用前置步驟**
> 適用時機：多個場景有相同的前置條件
```gherkin!
Feature: 會員下訂單
  會員必須先登入，才能將商品加入購物車並下訂
  
  Background:
    Given 會員 "小明" 已註冊，帳號為「ming@example.com」
    And 會員 "小明" 已登入系統
    And 商品 "可樂" 存在於系統中，價格為 30 元
    And 商品 "洋芋片" 存在於系統中，價格為 50 元
  
  Scenario: 會員下訂單包含單一商品
    Given 會員 "小明" 將商品 "可樂" 加入購物車，數量為 2
    When 會員 "小明" 下定訂單
    Then 訂單的訂單金額總額為 60 元
    And 訂單的擁有者為會員 "小明"
  
  Scenario: 會員下訂單包含多項商品
    Given 會員 "小明" 將商品 "可樂" 加入購物車，數量為 3
    And 會員 "小明" 將商品 "洋芋片" 加入購物車，數量為 2
    When 會員 "小明" 下定訂單
    Then 訂單的訂單金額總額為 190 元
    And 訂單的擁有者為會員 "小明"
```

**注意事項：**
- Background 會在每個 Scenario 執行前運行
- 只放共用的前置條件，不要放測試邏輯

---

### **▶ Docstring：傳遞複雜結構**
> 適用時機：需要傳遞 JSON、XML、Markdown 等多行文本
```gherkin=
Scenario: 建立會員資料
  Given 系統接收到以下 JSON 資料
    """
    {
      "name": "小明",
      "email": "ming@example.com",
      "preferences": {
        "newsletter": true,
        "notifications": false
      }
    }
    """
  When 建立新會員
  Then 會員資料儲存成功
```

**格式規則：**
- 使用三個雙引號 """ 包裹
- 保持原始格式（包括縮排和換行）


---

### **▶ Data Table：指定批次參數**
> 適用時機：需要一次性設定多筆結構化資料
```gherkin=
Scenario: 批次新增商品
  Given 系統中有以下商品
    | 商品名稱 | 價格 | 庫存 |
    | 可樂     | 30   | 100  |
    | 洋芋片   | 50   | 50   |
    | 礦泉水   | 20   | 200  |
  When 使用者查詢所有商品
  Then 系統回傳 3 筆商品資料
```
**與 Examples 的區別：**
- Data Table：在單一步驟中傳遞多筆資料
- Examples：定義多組完整的場景參數


---

### **▶ 使用星號（*）取代關鍵字 (And)**
```gherkin=
Scenario: 完成購物
  Given 我正在購物
  * 我有雞蛋
  * 我有牛奶
  * 我有奶油
  When 我檢查清單
  Then 我不需要其他東西
```
**何時使用：**
- 多個連續的 And 步驟
- 列舉類型的步驟
- 強調步驟的並列關係


<br />

## 如何選擇合適的架構？
**問題一：這個功能有多條業務規則需要處理嗎？**
- 否 → 使用 Feature + Scenario
- 是 → 使用 Feature + Rule + Scenario

**問題二：同一條規則需要驗證多組測資嗎？**
- 否 → 維持 Scenario
- 是 → 升級 Scenario Outline + Examples

**問題三：多個場景有相同的前置步驟嗎？**

- 是 → 加入 Background

```
你的功能有多條業務規則嗎？
├─ 否 → 使用【基礎層】Feature + Scenario
└─ 是 → 使用【中階層】Feature + Rule + Scenario
         │
         └─ 需要驗證多組測資嗎？
            ├─ 否 → 維持 Scenario
            └─ 是 → 升級【進階層】Scenario Outline + Examples

有重複的前置步驟嗎？
└─ 是 → 加入 Background

需要批次資料嗎？
└─ 是 → 使用 Data Table

需要傳遞複雜結構（JSON/XML）嗎？
└─ 是 → 使用 Docstring
```

## BDD 實踐
### 實踐流程
```
1. 三方會議（Three Amigos）
   PM + RD + QA 共同討論需求
   ↓
2. 用 Gherkin 撰寫場景
   將需求轉換為 Given-When-Then 格式
   ↓
3. 開發 Step Definitions
   將 Gherkin 步驟連接到實際代碼
   ↓
4. 執行測試
   Cucumber/SpecFlow/Behave 執行測試
   ↓
5. 持續更新
   需求變更時同步更新 Gherkin 文件
```

### 撰寫原則
#### 1. 使用業務語言，避免技術細節
  -- ❌ `Given 資料庫中 users 表有一筆 id=1 的記錄`
  -- ✅ `Given 會員 "小明" 已註冊`

#### 2. 每個 Scenario 保持獨立
-- 不依賴其他場景的執行結果
-- 可以任意順序執行

#### 3. 一個 Scenario 只驗證一件事
-- 避免過長的場景
-- 拆分複雜邏輯為多個 Rule

#### 4. 善用參數化
-- 相似場景使用 Scenario Outline
-- 減少重複代碼

#### 5. 保持 Step 的原子性
-- 每個步驟做一件事
-- 便於重用和維護

#### ※ 常見陷阱
- 過度技術化 - Gherkin 應該讓非技術人員也能理解
- 步驟耦合 - Given/When/Then 步驟互相依賴內部狀態
- 缺乏具體性 - 使用模糊的詞彙如「正確的」「適當的」
- 過度使用 Background - 導致場景難以獨立理解

<br />

## 總結
- BDD 是一種開發方法論，強調用行為描述需求
- Gherkin 是實現 BDD 的標準化語言
- 兩者的關係：沒有 BDD，Gherkin 只是語法；沒有 Gherkin，BDD 難以標準化實踐
- 選擇合適的架構層次（基礎/中階/進階）取決於功能複雜度
- 善用進階技巧（Background、Data Table、Docstring）提升可維護性
- BDD 的核心價值在於讓所有角色對需求有一致的理解