BDD(行為驅動開發)
是什麼?
BDD(Behavior-Driven Development)是 Dan North 提出的開發方法論,從使用者行為的角度定義軟體的預期行為,使用 Given-When-Then 格式撰寫可執行的規格。
ℹ️BDD vs TDD
TDD 從技術角度出發(測試函式),BDD 從使用者行為出發(測試場景)。BDD 是 TDD 的延伸和補充,不是取代。
核心觀念
- Given-When-Then:描述場景的三段式結構——Given(系統的初始狀態/前提條件)、When(使用者或系統觸發的動作)、Then(預期的結果或狀態變化)。對應到 AAA 模式:Given = Arrange、When = Act、Then = Assert
- Gherkin 語法:一種結構化的自然語言格式,用來撰寫 BDD 場景。關鍵字包括
Feature、Scenario、Given、When、Then、And、But。Gherkin 支援多國語言,可以用中文寫場景 - Feature File:用 Gherkin 寫成的場景檔案(
.feature),是活的文件(Living Documentation)。每個 Feature 對應一個使用者故事(User Story),包含多個 Scenario - Step Definition:把 Gherkin 語句對應到實際的測試程式碼。一個 Step Definition 可以被多個 Scenario 共用,用正則表達式擷取參數
- Ubiquitous Language:團隊共同使用的業務語言,消除溝通落差。BDD 場景用業務語言而非技術語言描述——寫「顧客點擊結帳」而不是「POST /api/checkout」
常見誤區
⚠️BDD 的陷阱
- BDD 不只是 Gherkin:BDD 的核心是團隊協作和行為思維,Gherkin 只是工具。沒有協作的 Gherkin 就是多餘的語法
- 場景寫太技術化:
Given database has record with id=1不是 BDD。用業務語言:Given a registered customer - 場景太多太細:不是每個功能都需要 BDD 場景。只用在有溝通價值的核心業務流程
- Step Definition 變成 God Class:每個 Step 應該簡短,複雜邏輯放到 Page Object 或 Helper
BDD 流程
Discovery(探索)
PM、QA、Dev 一起討論功能需求,用具體例子釐清行為
Formulation(定義)
把討論結果寫成 Given-When-Then 格式的場景
Automation(自動化)
用 SpecFlow/Cucumber 把場景變成可執行的測試
Implementation(實作)
用 TDD 實作功能,讓 BDD 場景通過
Living Documentation
場景檔案成為持續更新的活文件,所有人都看得懂
程式碼範例
Gherkin 場景(Feature File)
Feature: 購物車結帳
作為一個線上購物的顧客
我想要在購物車結帳
以便完成購買流程
Scenario: 一般顧客購買單一商品
Given 顧客 "Alice" 已登入
And 購物車中有 1 件 "TypeScript 入門書" 單價 500 元
When 顧客點擊結帳
Then 訂單總額應為 500 元
And 訂單狀態應為 "已建立"
Scenario: VIP 顧客享有折扣
Given VIP 顧客 "Bob" 已登入
And 購物車中有 1 件 "TypeScript 入門書" 單價 500 元
When 顧客點擊結帳
Then 訂單總額應為 400 元
And 應顯示 "VIP 折扣 20%"C#(xUnit + SpecFlow)
// Step Definitions — 把 Gherkin 對應到程式碼
[Binding]
public class CheckoutSteps
{
private Customer _customer;
private ShoppingCart _cart;
private Order _order;
[Given(@"顧客 ""(.*)"" 已登入")]
public void GivenCustomerLoggedIn(string name)
{
_customer = new Customer(name, CustomerType.Regular);
_cart = new ShoppingCart(_customer);
}
[Given(@"VIP 顧客 ""(.*)"" 已登入")]
public void GivenVipCustomerLoggedIn(string name)
{
_customer = new Customer(name, CustomerType.Vip);
_cart = new ShoppingCart(_customer);
}
[Given(@"購物車中有 (\d+) 件 ""(.*)"" 單價 (\d+) 元")]
public void GivenCartHasItem(int qty, string name, decimal price)
{
_cart.AddItem(new Product(name, price), qty);
}
[When(@"顧客點擊結帳")]
public void WhenCustomerCheckout()
{
_order = _cart.Checkout();
}
[Then(@"訂單總額應為 (\d+) 元")]
public void ThenOrderTotalShouldBe(decimal expected)
{
Assert.Equal(expected, _order.Total);
}
[Then(@"訂單狀態應為 ""(.*)""")]
public void ThenOrderStatusShouldBe(string status)
{
Assert.Equal(status, _order.Status);
}
}TypeScript(Jest + Cucumber)
import { Given, When, Then } from "@cucumber/cucumber";
let customer: Customer;
let cart: ShoppingCart;
let order: Order;
Given("顧客 {string} 已登入", (name: string) => {
customer = new Customer(name, "regular");
cart = new ShoppingCart(customer);
});
Given("購物車中有 {int} 件 {string} 單價 {int} 元", (qty, name, price) => {
cart.addItem(new Product(name, price), qty);
});
When("顧客點擊結帳", () => {
order = cart.checkout();
});
Then("訂單總額應為 {int} 元", (expected: number) => {
expect(order.total).toBe(expected);
});Python(pytest + pytest-bdd)
from pytest_bdd import given, when, then, scenario
@scenario("checkout.feature", "一般顧客購買單一商品")
def test_regular_customer_checkout():
pass
@given("顧客 \"Alice\" 已登入", target_fixture="cart")
def customer_logged_in():
customer = Customer("Alice", CustomerType.REGULAR)
return ShoppingCart(customer)
@given("購物車中有 1 件 \"TypeScript 入門書\" 單價 500 元")
def cart_has_item(cart):
cart.add_item(Product("TypeScript 入門書", 500), 1)
@when("顧客點擊結帳", target_fixture="order")
def customer_checkout(cart):
return cart.checkout()
@then("訂單總額應為 500 元")
def order_total_should_be(order):
assert order.total == 500關係圖
此圖展示 BDD 的完整流程鏈:從 Given-When-Then 格式出發,用 Gherkin 語法寫成 Feature File,透過 Step Definition 對應到測試程式碼,最終用 TDD 實作功能讓場景通過。注意:BDD 的價值不只在程式碼層,更在於 Discovery 階段的團隊溝通。
BDD vs 普通 Unit Test:何時選 BDD
| 判斷條件 | 用 BDD | 用普通 Unit Test | |----------|--------|-----------------| | 需要 PM/QA 參與討論場景 | 是 | 否 | | 核心業務流程(如結帳、退款) | 是 | 否 | | 底層工具函式、數學計算 | 否 | 是 | | 需要活文件讓非技術人員理解 | 是 | 否 | | 團隊沒有 PM/QA 協作的文化 | 否(先建文化再用工具) | 是 |
實戰補充
💡資深開發者的經驗
- Three Amigos 會議:每個功能開始前,PM + QA + Dev 三方一起討論具體例子。這個「Discovery」階段才是 BDD 的最大價值。
- 不是所有測試都要用 BDD:BDD 場景適合核心業務流程。底層的技術邏輯用普通的 Unit Test 就好。
- Gherkin 的維護成本:Feature File + Step Definition 是雙倍的維護量。只在「溝通價值 > 維護成本」時使用。
- 常用工具:C# 用 SpecFlow(已被 Reqnroll 接替),JS/TS 用 Cucumber.js,Python 用 pytest-bdd 或 Behave。
理解測驗
🤔 BDD 的 Given-When-Then 分別代表什麼?
🤔 BDD 和 TDD 的主要差異是什麼?
🤔 什麼情境最適合使用 BDD?
重點整理
💡一句話記住
口訣:「假設...當...那麼...」—— 三個人坐下來把故事說清楚,BDD 的價值在溝通不在語法。
| 概念 | 說明 | |------|------| | Given-When-Then | 前提-動作-預期結果,BDD 的核心格式 | | Gherkin | 結構化的自然語言語法 | | Feature File | 用 Gherkin 寫成的可執行規格 | | Step Definition | 把 Gherkin 對應到測試程式碼 | | 核心好處 | 促進團隊溝通、活的文件 | | 代價 | Feature + Step 雙倍維護成本,不適合底層技術測試 |