安安~我是ChiYu~
昨天比較類別拆分時,控制組、行數上限與內聚拆分都能把升級通知做對,也都通過最後的行為驗證。
若只看終點,三種結構都像是成功的候選。可是最後一片綠燈,只能證明它們抵達了終點,沒有說明途中累積了多少尚未驗證的修改,也沒有告訴我,走偏時得退回多遠。
Agent 可能一次修改兩百多行,完成 Production Code 後才補 Tests;也可能先取得有效 Red,再開始實作。兩種做法最後都能顯示綠燈,開發過程留下的證據與還原範圍卻完全不同。
九次實驗跑完後,Direct 三次都把功能做對;TDD 三次都先取得有效 Red,再完成 Green;TCR 則有一次依規則回復兩個失敗批次,最後停在尚未完成整項任務的 Green 狀態。這項 24 小時邊界需求,我最後採用 TDD Run 02。
原因不是 TDD 這個名稱比較正統,而是它先讓舊 Production 對新需求變紅,又用同一份時間快照完成邊界判斷;相較之下,Direct 缺少中途證據,本次 TCR 的最後一批也仍然太大。
今天要比較的,就是測試介入時間如何影響修改範圍、過程證據與還原成本。
《無瑕的程式碼 第二版》的〈測試紀律〉把測試放進整個開發週期。功能寫完後的驗收,只是其中一個階段。
第一項紀律是 TDD,全名為 Test-Driven Development,也就是測試驅動開發。它可以整理成三個動作:
這就是常聽到的 Red、Green、Refactor:Red 先顯示需求缺口,Green 用最少修改補上行為,Refactor 則在行為受到保護後整理 Code。
第二項紀律是 TCR,全名為 Test && Commit || Revert。每一小步都必須維持測試通過;通過就建立 Commit,失敗就放棄這一小批修改,回到上一個可以工作的狀態。
第三項紀律是 Small Bundles,繁體中文版譯為「小批次封裝」。它要求每次只處理容易理解、驗證與還原的一小批修改。Commit 數量只能當成線索;還要確認每一批承擔幾項行為,以及失敗時必須放棄多少內容。
這三項紀律共同控制的是回饋速度與錯誤半徑。測試愈早提供回饋,開發者就愈早知道目前的理解能不能承接下一步,也比較不必在一大批修改裡慢慢找錯。
不過,測試資料、需求理解或驗收條件一旦寫錯,再嚴格的紀律也可能把開發帶往錯誤方向。我把書末〈Clean Code 大辯論〉中的不同立場讀成這項提醒:TDD 仍要接受專案情境與實際成本的檢驗。今天不是替三種節奏排永久名次,而是看它們分別控制了什麼。
Uncle Bob 在近期訪談裡也重新調整了自己操作 Agent 的方式。他仍然重視 TDD,卻不再要求 Agent 完整模仿人類「先寫一小段 Test,再補一小段 Production Code」的微型節奏;他可以接受 Agent 先完成一個函式,再替這個函式補上測試。
調整的是工作粒度,測試品質沒有被拿掉。他的流程仍保留 Unit Test、Gherkin、Coverage、Mutation Testing 與 QA 等多層驗證。
我在意的也是這個區別:TDD 的價值包含提早說清楚答案、縮短回饋與限制錯誤範圍;人類為了維持專注形成的每個操作細節,未必都要原封不動套在 Agent 身上。
所以今天不會用「有沒有完整模仿 TDD 儀式」替候選評分。我會檢查 Red 是否真的指出需求缺口、Green 有沒有削弱測試、失敗時能退回多遠,以及最後的獨立 Oracle 能不能抓住錯誤。價值要守住,節奏則用實驗重新校準。
三組都會寫測試,也都要通過相同的最終驗證。差異集中在 Agent 何時看見測試結果,以及失敗後哪些修改還能留下:
| 開發節奏 | Tests 何時介入 | 測試通過後 | 測試失敗後 | 本篇要觀察什麼 |
|---|---|---|---|---|
| 直接完成(Direct Completion) | Production Code 與 Tests 可以在同一階段完成,最後才整批驗證 | 全部內容建立一個 Commit | 整批重新判斷或放棄 | 一次完成能不能做對,以及還原範圍有多大 |
| TDD | Production Code 修改前先取得有效 Red | 補最小 Green,再進入 Refactor | 保留 Red,依失敗原因補足行為 | 測試是否先看見需求缺口 |
| TCR | 每一小批修改後立刻測試,全程維持 Green | 立即 Commit | 捨棄這一批,回到上一個 Green 狀態 | 批次是否真的夠小,以及失敗能不能安全回復 |
本文把「測試全部通過、可以獨立保留,也能安全回復的 Git 狀態」稱為 Green Checkpoint。這是本次實驗用來比較還原能力的名稱。
TCR 名稱裡的 revert,指的是「測試失敗後回到上一個 Green 狀態」,不限定必須執行哪一條 Git 指令。
這次失敗的修改尚未 Commit,因此我使用 git restore <明確檔案>,把指定檔案還原到目前的 HEAD。它不會建立新 Commit;git revert 則會用一個新的反向 Commit 抵銷先前修改,兩者用途不同。
本篇採用嚴格維持 Green 的 TCR 版本。TCR 還有其他實作變體,今天的結果只適用於這份 Prompt 與操作規則。

圖:三種節奏的差異,在於紅燈何時出現、修改何時提交,以及失敗時要放棄多少內容。
實驗從昨天接受的內聚版本開始。Work Item API 原本會找出到期且未完成的項目,標記為 Overdue,再傳送一般逾期通知。今天新增一條規則:
Priority == "High"且至少逾期 24 小時時,改走SendEscalationAsync;其他項目維持SendOverdueAsync。
表面上只多一個條件,實際上同時帶來六種測試壓力:
| 壓力 | 要守住的行為 |
|---|---|
| 精確邊界 | 剛好 24 小時要升級,少一個 Tick 仍走一般通知 |
| 分流 | High 與非 High 必須走不同通知路徑 |
| 重跑 | 已是 Overdue 的項目仍要依相同規則再次通知 |
| 失敗 | 通知回傳 false 時仍要儲存 Overdue |
| 副作用順序 | Exception、Cancellation 與通知先於 Save 的順序不能漂移 |
| 外部契約 | HTTP Route、Status Code、Response JSON 與 SQLite Schema 維持不變 |
.NET 的一個 Tick 是 100 奈秒。本專案把 DateTimeOffset 以 Unix millisecond 寫進 SQLite,因此只差一個 Tick 的兩筆時間,經過資料庫寫入再讀回的 Database Round-trip 後,可能落在同一個毫秒。
我故意保留這個精度陷阱。它不只測試 TDD 與 TCR 會不會忠實執行 Tests,也能讓我看見:測試資料寫錯時,嚴格紀律究竟會怎麼反應。
這六種壓力最後被拆成八項可執行的驗收條件,並原樣寫入三組共用 Prompt。三種節奏面對的是同一份產品答案,差異只落在測試何時介入。
三組候選都只能修改四個既有檔案:Production 的 OverdueWorkItemProcessor.cs、NotificationGateway.cs,以及 Tests 的 ProcessOverdueBehaviorTests.cs、WorkItemsApiFactory.cs。同時禁止新增套件、資料庫 Migration、Strategy、Factory、第二個 Processor、第二套 Model 或新架構層。
Migration 是以版本化方式改變資料庫結構。這次先排除 Schema 變更,避免資料庫風險干擾三種測試節奏的比較。
這些限制是為了隔離開發節奏,不代表正式專案永遠只能修改四個檔案。真實需求仍要依 Repository 情境決定合理範圍。
三種節奏最後都會執行 Tests,但那些 Tests 也是候選的一部分。若 Agent 同時解讀需求、撰寫測試與宣布完成,就可能用同一份誤解驗證自己。因此主流程另外保留三道互相獨立的檢查。
| 時間點 | 關卡 | 負責回答的問題 |
|---|---|---|
| Agent 動手前 | 事前凍結八項行為 | 什麼答案才可以接受? |
| Agent 開發中 | Direct、TDD 或 TCR 的流程規則 | 何時能改 Production、保留修改或退回? |
| Agent 完成後 | Host Oracle 與完整主流程驗證 | 候選能否通過一份不由自己撰寫的驗收? |
今天使用的 Host Oracle,是由 Agent 工作階段之外的主流程事先準備、完成後才注入的獨立驗收測試。它不依賴候選新增的方法名稱與類別結構,只檢查事前固定的外部行為。
主流程還會逐一執行套件還原、Release Build、完整 Tests、格式檢查、Diff 檢查與 API 基本流程驗證(Smoke Test)。Agent 的完成摘要只作過程紀錄,九份候選最後都要接受相同驗證。
三組固定使用 Codex GPT-5.6-SOL-HIGH,每次都從相同 Commit 建立全新 Worktree,也不沿用前一次對話。
共同 Prompt 先固定需求、允許檔案與最終驗證:
固定起點:
- Commit:fc06aa4f02840c0afb5008043b5f219787db8fb9
- Annotated Tag:day-11-clean-classes
- 模型:gpt-5.6-sol/high
需求:
1. High 且 DueAtUtc <= currentUtc - 24 小時,呼叫 SendEscalationAsync。
2. 剛好 24 小時必須升級;少一個 Tick 維持 SendOverdueAsync。
3. 非 High 一律維持 SendOverdueAsync。
4. 已是 Overdue 的符合項目重跑時,仍使用相同通知路徑。
5. 升級通知沿用 Attempt、Failure 與失敗 ID 順序。
6. 通知回傳 false 時仍儲存 Overdue;Exception 與 Cancellation 不變。
7. 通知仍發生在 SaveChangesAsync 之前。
8. HTTP Route、Status Code、Response JSON 與 SQLite Schema 不變。
允許範圍:
- Production:OverdueWorkItemProcessor.cs、NotificationGateway.cs
- Tests:ProcessOverdueBehaviorTests.cs、WorkItemsApiFactory.cs
- 不得新增檔案、套件、Migration、Strategy、Factory、第二個 Processor、
第二套 Model 或新架構層。
- 不得修改既有測試期待值來配合 Production Code。
最終驗證:
dotnet restore .\AiCleanCode.sln --locked-mode -p:NuGetAudit=false
dotnet build .\AiCleanCode.sln --no-restore --configuration Release
dotnet test .\AiCleanCode.sln --no-build --configuration Release
dotnet format .\AiCleanCode.sln --verify-no-changes --no-restore
git diff day-11-clean-classes...HEAD --check
.\scripts\run-series-baseline-smoke.ps1
三份 Prompt 只有以下開發節奏不同。
1. 可以先閱讀與規劃,再一次完成 Production Code 與 Tests。
2. 不建立刻意的 Red 階段,也不建立中途 Commit。
3. 全部完成後才執行完整驗證。
4. 驗證通過後建立一個 Commit。
這組模擬大量 AI Coding 常見的做法:把整項需求交給 Agent,完成後再檢查 Tests 與 Diff。Direct 是本次實驗的控制組,不代表品質一定較差。
Red:
1. Production Code 完全不能先改。
2. 先加入一個行為測試與最少的測試 Gateway 能力。
3. 測試必須辨認 24 小時等號邊界、一般通知與非 High 路徑。
4. 測試要因 Production 尚未路由升級通知而失敗。
5. 編譯錯誤、環境故障或無關斷言(Assertion)失敗都不算有效 Red。
Green/Refactor:
1. Red 確認後才允許修改 Production Code。
2. 只寫足以通過新測試的實作,再執行新測試與完整測試。
3. Green Commit 後才檢查命名、重複與可讀性。
4. 沒有具體整理理由就停止,不建立空的 Refactor Commit。
斷言(Assertion)是測試對結果提出的明確期待。若測試因套件、權限或編譯問題失敗,它沒有說明需求缺口,不能拿來充當 TDD 的 Red。
至少切成三個可以獨立通過測試的 Green 小批次:
1. 建立 Gateway 能力與相容的測試 Gateway。
2. 加入最小升級路由。
3. 加入升級、24 小時邊界、一般通知與重跑測試。
每一批的規則:
- 通過:檢查暫存內容後立即 Commit。
- 失敗:保存狀態、失敗測試與明確檔案的 Diff。
- 接著逐一對明確檔案執行 git restore,回到目前 HEAD。
- 還原後先確認 Worktree 乾淨,再把批次切小重新嘗試。
- 不得使用 git reset --hard、git clean、目錄級還原或萬用字元。
三份未縮寫 Prompt、九個原始 Session 與主流程輸出都保存在 Day 12 公開實驗資料。
三種節奏各執行三個彼此獨立的 Codex Session,共九次實驗。結果先放在同一張表裡:
| 節奏 | 三次執行結果 | 過程證據 | 批次與還原範圍 | 主流程獨立驗證 |
|---|---|---|---|---|
| Direct | 三次都完成需求 | 沒有刻意保留有效 Red | 每次只有一個 Commit,涵蓋 231~248 行 | 三次皆通過 |
| TDD | 三次都先取得有效 Red,再完成 Green | Red 前 Production Diff 都是 0 行 | 完整修改量為 125~136 行 | 三次皆通過 |
| TCR | Run 01/02 完成三個 Checkpoint;Run 03 發生兩次回復並停止 | 每一批都有測試與 Commit/回復紀錄 | Run 01/02 的第三批仍有 212/217 行;Run 03 停在兩個 Green Commit | 三份 Production 行為皆通過;Run 03 沒有宣告完成 |
Host Oracle 通過,只能證明 Run 03 已完成的 Production 行為符合事前契約。它沒有完成預定的第三個測試批次,因此這次執行應記為「依規則停止」,不能記為「整項任務完成」。
這不是把停止美化成成功。它只是誠實區分兩件事:已保留的兩個 Checkpoint 是綠的,但整項交付仍然缺少第三批測試。
三份 Direct 候選都正確完成需求,也不需要人工修補。直接完成仍然可能產生正確、可接受的 Code。
問題不在最終功能,而在失敗時缺少中途還原點。若 24 小時邊界寫錯,User 必須重新檢查涵蓋 Production、測試 Gateway 與行為測試的整份 Diff,再決定哪些內容能留下。
Direct 最後新增的 Tests 可以確認完成後的 Code 通過目前案例;它沒有留下實作前的有效 Red,因此無法回答 Tests 是否曾在舊 Production 上看見需求缺口。
三次 TDD 都先只修改 ProcessOverdueBehaviorTests.cs 與 WorkItemsApiFactory.cs。當時兩個允許的 Production 檔案沒有任何 Diff,新測試則因 EscalatedWorkItemIds 仍是空集合而失敗。
這三個 Red 都符合四項條件:
確認 Red 後,Agent 才加入 SendEscalationAsync 與通知選擇條件:
var currentUtc = timeProvider.GetUtcNow();
var dueIncompleteWorkItems = await FindDueIncompleteWorkItemsAsync(
currentUtc,
cancellationToken);
var processingSummary = await ProcessDueIncompleteWorkItemsAsync(
dueIncompleteWorkItems,
currentUtc.AddHours(-24),
cancellationToken);
在單筆處理時,再用同一個時間快照算出的門檻選擇通知:
var notificationSucceeded = string.Equals(
workItem.Priority,
"High",
StringComparison.Ordinal) &&
workItem.DueAtUtc <= escalationThresholdUtc
? await notificationGateway.SendEscalationAsync(workItem, cancellationToken)
: await notificationGateway.SendOverdueAsync(workItem, cancellationToken);
這段實作只擷取一次 currentUtc,查詢「是否已到期」與判斷「是否滿 24 小時」共用同一個時間基準。若每筆各自呼叫 GetUtcNow(),門檻附近的項目可能在同一批處理中讀到不同答案。
這次三份 TDD 候選的完整修改量為 125~136 行,少於 Direct 的 231~248 行;不過差異也受到測試組織方式影響,不能推論 TDD 固定能減少一半修改量。能直接確認的是:三次 TDD 都先讓舊 Production 因缺少目標行為而出現 Red。
TCR Run 01 與 Run 02 都完成三個 Green Checkpoint:
前兩步確實很小,也各自維持 Green。第三個 Checkpoint 分別增加 212 與 217 行,雖然可以獨立還原,仍然稱不上小批次。
這次結果顯示,三個 Commit 只代表流程留下三個還原座標,不能證明每一批都足夠小。還要檢查每批修改了哪些檔案、包含幾項行為,以及失敗時能不能整批放棄。
下一次執行 TCR 前,可以先替每個 Checkpoint 訂出行為範圍與停止條件,例如只處理一個邊界情境,或在 Diff 超過預期時先停止審查。這些數字應當是警報,不是固定品質分數。
TCR Run 03 沒有走到第三個 Commit,中間發生兩次回復。
第一次是測試 Runner 的篩選參數不相容,實際執行到 0 個測試。Agent 保存狀態後,依規則放棄該批修改。
第二次出在邊界測試資料:
var atBoundary = currentUtc.AddHours(-24);
var oneTickBeforeBoundary = atBoundary.AddTicks(1);
測試把 currentUtc 放在整毫秒。SQLite 儲存 DateTimeOffset 後,兩筆只差一個 Tick 的資料落在同一個毫秒,測試因此誤判一般通知路徑。Agent 再次放棄失敗批次,最後停在兩個乾淨的 Green Commit。
主流程事前凍結的 Host Oracle 把固定時間放在下一毫秒前一個 Tick:
var utcNow = new DateTimeOffset(2026, 9, 1, 8, 0, 0, TimeSpan.Zero)
.AddTicks(TimeSpan.TicksPerMillisecond - 1);
這樣「剛好 24 小時」與「少一個 Tick」經過 Database Round-trip 後,仍會落在相鄰毫秒。主流程注入這份 Oracle 後,Run 03 已完成的 Production 路由全部通過。
TCR 正確執行了 Commit 與回復規則,卻因測試資料錯誤而停止後續工作。這表示流程紀律只能忠實執行目前的 Oracle;User 仍要確認時間精度、邊界值與測試資料是否真的代表產品規則。
查看停在兩個 Green Commit 的 TCR Run 03 Diff。
Fresh Input Token 是扣除快取後,Agent 在該 Session 重新讀入的輸入量;Tool Calls 則是 Agent 呼叫檔案、終端機與其他工具的次數。
九次量測資料整理成範圍後如下:
| 節奏 | Fresh Input Token | Tool Calls | 執行時間 |
|---|---|---|---|
| Direct | 113,978~175,742 | 26~53 | 427.21~615.32 秒 |
| TDD | 125,762~139,680 | 31~50 | 644.64~708.26 秒 |
| TCR | 109,027~236,214 | 41~71 | 435.13~698.75 秒 |
這九次執行沒有出現「紀律愈嚴格,Token 就愈少」的穩定趨勢。TDD 多了 Red 階段,TCR 則增加測試、Commit 與回復操作。
Direct 與 TDD 使用 workspace-write,也就是只能在指定工作區內寫入的受限模式;TCR 為了寫入 Worktree 的 Git 內部資料,使用允許較廣泛本機檔案操作的 danger-full-access。三組的權限環境不同。
因此,這些數字只能描述本次執行成本,不能用來判定哪一種節奏天生比較省 Token、工具呼叫或時間。
完整九次量測資料、原始輸出與驗證結果收錄在 Day 12 實驗控制文件。
AI Agent 很容易回報「新增測試,全部通過」。這句話只能說明目前執行的案例是綠燈,還不足以回答測試在開發過程中發揮了什麼作用。
我會再追查五個問題:
| 檢查項目 | 審查時要看什麼 |
|---|---|
| 行為涵蓋 | 需求、等號邊界、失敗、副作用與重跑是否都有可執行案例? |
| 缺口辨識 | 新測試是否曾在舊 Production 上因正確理由失敗? |
| Oracle 獨立性 | 驗收條件是否在 Agent 動手前固定,或由另一個流程重新驗證? |
| 失敗定位 | 紅燈能不能直接指出漂移的行為? |
| 還原能力 | 失敗時要放棄幾個檔案、多少行,以及能回到哪個 Green Checkpoint? |
Coverage 可以協助找出完全沒走到的區域,測試數量也能描述規模。兩者都無法單獨確認 Assertion 是否正確、邊界資料是否可靠,或測試是否真的限制了需求。
L — Localized Change 局部變更:用失敗時要放棄多少內容衡量批次Direct 三份候選都只有一個 Commit;TCR Run 01 與 Run 02 都有三個 Commit。只數 Commit,TCR 看起來一定比較局部,可是第三個 Checkpoint 仍超過兩百行。
L — Localized Change 局部變更 在這裡檢查三件事:這一步承擔哪個行為、失敗時要放棄多少內容,以及能否回到上一個可工作的狀態。
User 應先替每一批寫清楚行為範圍、允許檔案、通過條件與還原方式。Agent 可以自主決定實作細節,但每個 Checkpoint 都必須能單獨理解、驗證與放棄。
A — Auditable by Evidence 實據可審:全綠之外,還要留下抵達綠燈的過程Direct、TDD 與 TCR 的 Production 行為最後都通過 Host Oracle,三種節奏留下的過程證據卻不同。
A — Auditable by Evidence 實據可審 要求我保留足以回答決策的資料:
把 Prompt、Diff、測試時序、Commit/回復、主流程 Gate 與人工接受理由放在一起,才能審查 Agent 的開發過程。只有一句 Tests Passed,無法回答 Agent 是否先改了 Production、Red 是否有效,或失敗時究竟退回了哪些內容。
今天的三種節奏沒有固定冠軍。我會按照需求風險、可還原範圍與測試速度選擇。
例如局部文案、明確 Mapping,或已有可靠回歸測試(Regression Tests)的小修改。回歸測試用來確認這次變更沒有破壞原本已成立的行為。
任務仍要固定允許範圍、不得改變的契約與最終 Gate。Diff 開始超出預期時,就停止並改用較小週期。
Direct 少了 Red 與中途 Checkpoint,流程步驟較少;相對地,還原點也較少。它適合整包放棄仍可接受的小修改,不適合把付款、權限、時間邊界或多項副作用塞進同一次交付。
像今天的 24 小時等號邊界、通知失敗仍要儲存,以及重跑時的通知路徑,都能先寫成可觀察行為。User 先確認 Red 的原因,Agent 再進入 Green,可以降低 Production 與 Tests 一起誤解需求的機率。
TDD 未必節省 Token,還會多一次 Red 執行與紀錄。我願意在付款、權限、狀態轉換、時間邊界與資料一致性需求上支付這個成本,因為 Red 能留下清楚的需求缺口與因果順序。
TDD 的 Red、Green、Refactor 會增加前置測試、執行與紀錄成本,這也是不少團隊過去對它有所保留的原因。進入 AI Coding 後,我反而更願意支付這段成本:人可以不親手輸入每一行 Code,仍要先把「什麼答案才正確」變成 Agent 能執行的測試。對我來說,這已經是使用 AI Coding 時不可或缺的能力。
官方指南不一定要求完整 TDD,方向卻很一致。GitHub 建議在 Repository Instructions 寫清楚建置、測試與驗證步驟。Anthropic 要求提供可執行的 Verification Criteria,也示範先寫出能重現問題的 Failing Test。OpenAI 則建議 Coding Agent 在修改後執行相關單元測試、型別或格式檢查、Build 與必要的 Smoke Test,無法執行時要說明原因。
三份指南的共同點,是 Agent 必須實際驗證結果,不能拿完成摘要代替證據。
TCR 最適合先當成練習小步開發與快速回饋的流程實驗。murex/TCR 專案也提醒,這套方法有助於練習 Baby Steps,直接套用到正式 Production Code 則更具挑戰。因此我不會只因需求風險高,就直接把所有開發升級成 TCR。
我會在修改失敗的代價很高,而且每一步都能由 Git 安全還原時,才把節奏收緊成 TCR。例如:
這些情境還要同時滿足三個條件:任務可以切成幾分鐘內完成的小步、測試能快速且穩定地指出錯誤、上一個 Green Commit 足以還原這批修改。缺少其中一項,TCR 只會增加 Commit、回復與工具呼叫。
Git 只能還原 Repository 內的檔案,不能收回已執行的資料庫 Migration、已送出的通知、付款或外部 API 寫入。
這些外部副作用還需要其他保護方式,例如在 Sandbox 隔離環境演練、先做不寫入真實系統的 Dry Run、準備 Rollback 方案,或執行一筆抵銷原操作的補償操作(Compensating Action)。完成後還要獨立查核外部狀態。TCR 能保護程式碼修改歷程,無法替外部世界倒帶。
回到這次需求,它同時具有業務規則、精確時間邊界、通知副作用與重跑行為,因此我採用 TDD。
三份 TDD 都有有效 Red,我最後採用 Run 02:
TimeProvider,門檻附近的多筆資料可能取得不同的現在時間。currentUtc,到期查詢與升級門檻共用同一份時間快照。讀者不必下載專案,也可以直接閱讀本文的 Prompt、結果與公開 Diff。若想從相同基準重現實驗,可以執行:
git clone https://github.com/eric861129/AI-CleanCode-API-Demo.git
Set-Location .\AI-CleanCode-API-Demo
git fetch origin --tags
git switch --detach day-11-clean-classes
git rev-parse HEAD
最後一行應該顯示:
fc06aa4f02840c0afb5008043b5f219787db8fb9
採用版本如下:
1f2f818ea677ee4f6c867aa86e21f94f40da5b61
day-12-testing-disciplines
可以直接切換到這個版本:
git fetch origin --tags
git switch --detach day-12-testing-disciplines
git rev-parse HEAD
最後一行應該顯示:
1f2f818ea677ee4f6c867aa86e21f94f40da5b61
Run 02 因為共用單一時間快照,並讓毫秒精度下的邊界資料保持可辨識,所以成為系列接續版本。若未來需求改成低風險 Mapping,或進入付款、權限等更高風險情境,仍應重新評估 Direct、TDD 或 TCR。
我不希望每次都重新告訴 Agent 什麼叫有效 Red、何時可以修改 Production,以及失敗後要怎麼回復。以下先把今天的校準結果整理成一份可放入 AGENTS.md 的 Policy 範例:
## Testing Discipline Policy
### 共同前置條件
- 開始實作前,先列出需求行為、等號邊界、失敗路徑、副作用順序與不得改變的契約。
- 高風險行為由 User 或主流程事前凍結獨立 Oracle;Agent 不得以自己的最終測試取代 Oracle。
- Agent 先依需求風險、可還原範圍與測試速度建議 Direct、TDD 或 TCR,並列出判斷理由。
- Repository 或 User 已指定節奏時,依指定流程執行;無法滿足條件時停止並回報。
### Direct
- 只用於低風險、既有 Regression Tests 足夠,而且可以整批放棄的小修改。
- 先限制允許檔案、不得改變的契約、最終 Gate 與單一 Commit 的最大範圍。
### TDD
- 業務規則、邊界值、狀態轉換或回歸風險明確時,優先考慮 TDD。
- Red 前不得修改 Production Code;Red 必須能編譯、能執行,並因缺少目標行為而失敗。
- 不得以削弱 Assertion、刪除案例、排除測試或改變既有期待值取得 Green。
- Green 只加入足以通過目標行為的修改;完整 Regression Tests 通過後才進入 Refactor。
### TCR
- 失敗代價高、任務能切成數分鐘的小步、測試快速可靠,而且每批都能由 Git 安全還原時,才採用 TCR。
- 每一批都從乾淨的 Green 狀態開始;通過就 Commit,失敗先保存測試輸出與 Diff。
- 失敗後逐一對明確檔案執行 git restore,回到目前 HEAD。
- 不得使用 git reset --hard、git clean、目錄級還原或萬用字元處理失敗批次。
- Commit 數量不代表 Small Bundles;每個 Checkpoint 都要回報行為、檔案、Diff 行數與可獨立還原的理由。
### 共同驗收
- 時間、精度、排序、文化特性與 Database Round-trip 的邊界測試,必須確認測試資料在實際 Provider 中仍可辨識。
- Agent 完成後,由主流程重新執行相同 Oracle、Build、完整 Tests、Format、Diff Check 與必要 Smoke。
- 最終回報包含 Red/Green 或 Commit/回復時序、失敗原因、人工修補與停止理由,不能只回報最後全綠。
這份 Policy 目前是文章中的示範,尚未寫入 Demo Repository 的 AGENTS.md。未來會再抽成獨立 SKILL,方便重複使用、版本控制與開源;讀者現階段可以先依自己的 Repository、測試速度與 Git 流程調整後使用。
回到標題,寫完再測與先測再改的差別,不是其中一種一定能寫對、另一種一定會寫錯。九次實驗裡,Direct 也能交出正確 Code。
真正的差異是:需求缺口何時第一次被看見、尚未驗證的修改會累積多少,以及失敗時有沒有清楚的回復位置。
這項 24 小時邊界需求最後採用 TDD Run 02,因為它先留下有效 Red、共用單一時間快照,也通過主流程獨立驗收。實驗沒有顯示 TDD 或 TCR 能穩定節省 Token;它們增加的是需求缺口、失敗位置與還原範圍的可見性。
這正好對應 L — Localized Change 局部變更 與 A — Auditable by Evidence 實據可審:User 先限制一次能放棄的範圍,再保留 Agent 如何抵達綠燈的證據。
系列第一篇提到的 Uncle Bob 兩則 X 貼文,也把測試、Coverage、Mutation Testing、Gherkin、品質指標與 QA Procedure 放進 Agent 的約束流程。單次 Tests 全綠只是其中一項,還不能代表整套驗證可信。
更麻煩的問題是:即使測試介入得很早,測試資料本身也可能答錯。
明天將繼續檢查〈整潔的測試〉與〈驗收測試〉:如果 Tests 命名混亂、彼此耦合,或使用錯誤的邊界資料,什麼樣的測試才值得交給 Agent 反覆執行?