什麼是測試驅動開發 (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、關鍵零件與點檢表:

BOM 頁籤:
Parentcompoents (拼錯)usageunit
PRD-001A0.5kg
PRD-001B2.0kg
PRD-001C3.0kg
Route 頁籤:
productSteplead time
PRD-001brend1.0
PRD-001casting1.0
PRD-001ovening2.0
PRD-001skiving0.5
Critical Component 頁籤: PRD-001 需要 mod-001
CheckList 頁籤: Machine avilibility: Y, SOP: Y

狀態庫 inventory.xlsx

追蹤生產現場的動態庫存與狀態(設備與關鍵零件狀態已修正為 OK):

Material(BOM) 頁籤 (多批次庫存):
Part numberbatchamount
Aa00019
Aa00028
Bb00018
Bb00026
Cc000113
Cc00028
Equipment 頁籤 (設備狀態):
equipmentStatus
brendOK
castingOK
oveningOK
skivingOK
CriticalComponent 頁籤:
序號 CRT0003 / 規格 mod-001 / 狀態 OK

2. 測試驅動代碼結構 (TDD Tests Structure)

本系統共有四個測試檔案,藉由測試來驅動功能開發:

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.pyvalidator.py 的完整協作。Loader 負責自 Excel 解析出 BOMItemRouteStep 等實體列表,並完美吸收 Excel 中諸如 compoentsserail 等拼寫錯誤,讓 Validator 可以使用標準英文欄位命名,乾淨且解耦地完成業務規則判斷。

6. 系統互動與 TDD 精神示意圖 (Interaction & TDD Loop)

下圖呈現了 Excel 檔案、載入器、領域模型、校驗引擎與測試代碼(含 Demo)之間的互動關係,以及 TDD methodology 所遵循的 Red-Green-Refactor 循環精神:

Excel 數據源 .xlsx 檔案 Excel 載入器 loader.py 領域實體模型 models.py 齊套驗證引擎 validator.py Demo 整合執行器 run_demo.py / test_integration.py 1. RED 撰寫失敗測試 2. GREEN 最小實作通過 3. REFACTOR 優化架構重構

說明: 系統運作時,run_demo.py(或整合測試)驅動 loader.py 讀取並轉換 Excel 檔案,產生 models.py 中定義的領域模型實體,再送入 validator.py 中檢核,最後回傳齊套確認結果。在開發流程上,則始終遵循「紅 (失敗測試) $\rightarrow$ 綠 (最小實作) $\rightarrow$ 重構 (架構優化)」的 TDD 循環。

TDD 的安全防護網

本案例完整展現了 TDD 的精髓:先建立測試案例保護邊界,再實作功能。在這種開發模式下,如果未來我們需要修改 Excel 欄位對應、變更設備狀態定義或優化多批次分配邏輯,我們只要一鍵執行測試套件即可立即抓出任何邏輯漏洞,確保重構零風險。