什麼是測試驅動開發 (TDD)?
Test-Driven Development (TDD) 是一種極致專注、嚴謹且以需求為導向的軟體開發方法論。它要求測試代碼必須在實際功能代碼之前誕生。
TDD 的設計哲學:測試先行 (Test-First)
在傳統的瀑布流或敏捷開發中,開發人員通常是「先編寫功能程式碼,有空再補寫測試」,甚至完全仰賴人工測試(Test-Last)。
TDD 顛覆了這個模式。 它將「測試腳本」作為需求的具體物理定義。開發人員藉由編寫一個必定失敗的測試,來強迫自己釐清邊界條件、輸入輸出規格與模組介面,這確保了每一行程式碼的產出都是為了讓測試通過,絕無冗餘。
開發思維大對決:傳統模式 vs TDD 模式
傳統開發 (Test-Last)
- 先寫程式碼,再以「拼圖式」手動補寫測試
- 程式碼易與環境高度耦合,導致後續極難編寫自動測試
- 重構程式碼時戰戰兢兢,深怕牽一髮而動全身
- 規格文件隨時間老化,無法反映程式碼的真實現狀
測試驅動 (Test-First)
- 先編寫測試腳本,再編寫剛好符合要求的功能碼
- 天生強迫實踐關注點分離與介面解耦,提高架構彈性
- 擁有 100% 的自動化自測安全網,隨時隨地大膽重構
- 測試案例即是「活的文件」,隨時反映真實的預期行為
TDD 為專案帶來的核心商業價值
1. 減少 60% - 90% 的缺陷漏出率
在早期就抓出邊界錯誤與型態異常,避免將系統缺陷漏出至後期昂貴的人工測試與線上生產環境中。
2. 賦予極佳的系統重構與升級彈性
軟體變更或補丁發布時,一鍵觸發自動迴歸測試,確保「既有的穩定功能沒有因重構而損壞」。
TDD 實戰案例:MES 工單齊套確認系統
本案例以製造執行系統 (MES) 中的「工單齊套校驗」為對象,使用 TDD 方法論開發。系統負責在工單投產前,檢查靜態配置(CheckList、Route)與動態資源(BOM 庫存、設備狀態、關鍵零件)是否已全數到位。
1. MES 齊套驗證數據源 (Excel 內容說明)
系統會自外部載入兩份 Excel 試算表作為輸入來源,其內部結構與測試資料如下:
配置檔 WO_validation.xlsx
定義工單所需的 BOM 用量、工序 Route、關鍵零件與點檢表:
| Parent | compoents (拼錯) | usage | unit |
|---|---|---|---|
| PRD-001 | A | 0.5 | kg |
| PRD-001 | B | 2.0 | kg |
| PRD-001 | C | 3.0 | kg |
| product | Step | lead time |
|---|---|---|
| PRD-001 | brend | 1.0 |
| PRD-001 | casting | 1.0 |
| PRD-001 | ovening | 2.0 |
| PRD-001 | skiving | 0.5 |
PRD-001 需要 mod-001CheckList 頁籤:
Machine avilibility: Y, SOP: Y
狀態庫 inventory.xlsx
追蹤生產現場的動態庫存與狀態(設備與關鍵零件狀態已修正為 OK):
| Part number | batch | amount |
|---|---|---|
| A | a0001 | 9 |
| A | a0002 | 8 |
| B | b0001 | 8 |
| B | b0002 | 6 |
| C | c0001 | 13 |
| C | c0002 | 8 |
| equipment | Status |
|---|---|
| brend | OK |
| casting | OK |
| ovening | OK |
| skiving | OK |
序號
CRT0003 / 規格 mod-001 / 狀態 OK
2. 測試驅動代碼結構 (TDD Tests Structure)
本系統共有四個測試檔案,藉由測試來驅動功能開發:
-
1. 領域模型檢驗:
test_models.py定義與測試最基礎的 Python 領域對象(Domain Entities)。例如
WorkOrder、BOMItem與MaterialInventory,並確保變數名稱、型別標示(Type Hinting)如iPlannedQty與sProductCode的正確性。 -
2. 解析與映射檢驗:
test_loader.py測試讀取 Excel 檔案的程式。此測試驅動開發出容錯映射機制,使程式內部使用標準英文拼寫(如
sMaterialId、sSerialNumber),但從 Excel 讀取時能自動對照compoents與serail等拼寫錯誤的欄位。 -
3. 業務邏輯檢驗:
test_validator.py測試核心業務規則。例如:檢核清單必須全為
Y;製程步驟所有對應設備狀態為OK;以及多批次庫存累加(加總同一料號在不同 Batch 的數量,以確認是否大於等於計劃產量 * BOM用量)。 -
4. 系統整合檢驗:
test_integration.py端到端測試(E2E Test)。呼叫讀取模組載入實際的
WO_validation.xlsx與inventory.xlsx,並將載入資料送入驗證引擎,驗證產量 5 時整體驗收通過(返回True),產量 10 時因庫存不足而拒絕(返回False)。
3. Demo 展示與測試執行 (run_demo.py)
我們可以透過專用的 run_demo.py 來執行齊套檢核,並在主機終端呈現運作效果:
# 執行驗證引擎的單元測試
$ python -m unittest discover -s tests
....................
----------------------------------------------------------------------
Ran 20 tests in 0.320s
OK
# 執行實際的 Demo 展示程式
$ python run_demo.py
Loading data from Excel sheets...
Data loaded successfully.
--- Test Case 1: Planned Quantity = 5 ---
Expected: True, Actual Result: True
--- Test Case 2: Planned Quantity = 10 ---
Expected: False, Actual Result: False
3.5. 工單輸入與校驗引擎調用關係 (run_demo.py 與 validator.py)
當我們執行 run_demo.py 時,程式首先會接受輸入的工單資訊(例如工單產量),隨後呼叫齊套校驗引擎 validator.py 中由單元測試 test_validator.py 所驗證之規則進行比對。以下為該核心調用片段:
# 摘自 run_demo.py
# 1. 定義要校驗的工單計劃產量(例如輸入 5 與 10)
iQtySuccess: int = 5
iQtyFail: int = 10
# 2. 直接呼叫 validator.py 中定義的統一齊套校驗入口
# 該引擎背後運行的業務邏輯由 tests/test_validator.py 的測試用例保駕護航
bResultSuccess: bool = validate_work_order_kitting(
iPlannedQty=iQtySuccess,
lstBOM=lstBOM,
lstRoute=lstRoute,
lstChecklist=lstChecklist,
lstCCRequirements=lstCCReq,
lstMaterialInv=lstMaterialInv,
lstEquipInv=lstEquipInv,
lstCCInv=lstCCInv
)
說明: 此流程展示了執行程式(run_demo.py)與 TDD 開發成果之間的配合。在 run_demo.py 中設定輸入條件後,直接調用 validator.py。由於該驗證器內部所有核心邏輯早已在單元測試 test_validator.py 中被充分驗證(多批次庫存累加、各狀態為 OK 等),我們在 Demo 中可以直接信賴其結果。
4. 領域實體與驗證邏輯交互代碼 (以 test_validator.py 為例)
在 TDD 的開發中,我們藉由單元測試先行定義領域模型與驗證邏輯的互動。以下摘自 tests/test_validator.py 中驗證 BOM 庫存的單元測試片段:
# 摘自 tests/test_validator.py
from src.domain.models import BOMItem, MaterialInventory
from src.domain.validator import validate_bom_inventory
def test_bom_inventory_sufficient_should_pass(self) -> None:
# Arrange - 定義工單產量、BOM用量需求(使用由 models.py 定義之領域實體)
iPlannedQty: int = 10
lstBOM: list[BOMItem] = [
BOMItem(sParentProduct="PRD-001", sMaterialId="A", dUsage=0.5, sUnit="kg"),
BOMItem(sParentProduct="PRD-001", sMaterialId="B", dUsage=2.0, sUnit="kg")
]
# 定義模擬的庫存狀況(由 MaterialInventory 實體組成)
lstInventory: list[MaterialInventory] = [
MaterialInventory(sMaterialId="A", sBatchId="a1", dAmount=3.0),
MaterialInventory(sMaterialId="A", sBatchId="a2", dAmount=3.0),
MaterialInventory(sMaterialId="B", sBatchId="b1", dAmount=15.0),
MaterialInventory(sMaterialId="B", sBatchId="b2", dAmount=10.0)
]
# Act - 呼叫待測試的驗證邏輯
bIsBOMOk: bool = validate_bom_inventory(
iPlannedQty=iPlannedQty,
lstBOM=lstBOM,
lstInventory=lstInventory
)
# Assert - 斷言驗證結果符合預期 (必須為 True)
self.assertTrue(bIsBOMOk)
說明: 此片段展示了測試程式如何主動實例化在 models.py 定義的資料結構,並將其作為參數輸入給 validator.py 的函數進行校驗。在編寫此測試時,validate_bom_inventory 還不存在(或僅有回傳 False 的空殼),這強迫開發者在動手寫程式碼前,先敲定函式的參數介面與型別。
5. 整合與 Excel 載入驗證代碼 (以 test_integration.py 為例)
在 E2E 整合測試或展示腳本中,我們透過 loader.py 讀取實際的 Excel 檔案,並把解析後的資料列表傳入整合後的齊套驗證引擎中。以下摘自 tests/test_integration.py 的測試片段:
# 摘自 tests/test_integration.py
from src.domain.loader import (
load_bom_items, load_route_steps, load_checklist_items,
load_critical_component_requirements, load_material_inventories,
load_equipment_inventories, load_critical_component_inventories
)
from src.domain.validator import validate_work_order_kitting
class TestWorkOrderKittingIntegration(unittest.TestCase):
def setUp(self) -> None:
self.sValidationPath: str = r"c:\AI\Antigravity\anti\TDD_method\TDD\WO_validation.xlsx"
self.sInventoryPath: str = r"c:\AI\Antigravity\anti\TDD_method\TDD\inventory.xlsx"
# 1. 透過 loader 載入實體 Excel 數據,並由 loader 完成欄位拼寫轉換
self.lstBOM = load_bom_items(self.sValidationPath)
self.lstRoute = load_route_steps(self.sValidationPath)
self.lstChecklist = load_checklist_items(self.sValidationPath)
self.lstCCReq = load_critical_component_requirements(self.sValidationPath)
self.lstMaterialInv = load_material_inventories(self.sInventoryPath)
self.lstEquipInv = load_equipment_inventories(self.sInventoryPath)
self.lstCCInv = load_critical_component_inventories(self.sInventoryPath)
def test_e2e_sufficient_inventory_should_pass(self) -> None:
# Arrange - 設定計劃生產量為 5 (根據 Excel,此產量庫存足夠)
iPlannedQty: int = 5
# Act - 呼叫單一入口齊套校驗函式
bIsKittingComplete: bool = validate_work_order_kitting(
iPlannedQty=iPlannedQty,
lstBOM=self.lstBOM,
lstRoute=self.lstRoute,
lstChecklist=self.lstChecklist,
lstCCRequirements=self.lstCCReq,
lstMaterialInv=self.lstMaterialInv,
lstEquipInv=self.lstEquipInv,
lstCCInv=self.lstCCInv
)
# Assert - 驗證結果應為 True (齊套通過)
self.assertTrue(bIsKittingComplete)
說明: 此整合測試片段展示了 loader.py 與 validator.py 的完整協作。Loader 負責自 Excel 解析出 BOMItem、RouteStep 等實體列表,並完美吸收 Excel 中諸如 compoents、serail 等拼寫錯誤,讓 Validator 可以使用標準英文欄位命名,乾淨且解耦地完成業務規則判斷。
6. 系統互動與 TDD 精神示意圖 (Interaction & TDD Loop)
下圖呈現了 Excel 檔案、載入器、領域模型、校驗引擎與測試代碼(含 Demo)之間的互動關係,以及 TDD methodology 所遵循的 Red-Green-Refactor 循環精神:
說明: 系統運作時,run_demo.py(或整合測試)驅動 loader.py 讀取並轉換 Excel 檔案,產生 models.py 中定義的領域模型實體,再送入 validator.py 中檢核,最後回傳齊套確認結果。在開發流程上,則始終遵循「紅 (失敗測試) $\rightarrow$ 綠 (最小實作) $\rightarrow$ 重構 (架構優化)」的 TDD 循環。
TDD 的安全防護網
本案例完整展現了 TDD 的精髓:先建立測試案例保護邊界,再實作功能。在這種開發模式下,如果未來我們需要修改 Excel 欄位對應、變更設備狀態定義或優化多批次分配邏輯,我們只要一鍵執行測試套件即可立即抓出任何邏輯漏洞,確保重構零風險。