工商時間

TDD、E2E、自動化驗證——課程裡每個功能都是這樣長出來的,不是先寫完再祈禱。

《AI 賦能全端開發:從零打造企業級智慧應用》:用同一套 AI CRM 專案,從 Spring Boot 後端、React 前端到資料庫與權限,一路加上 Spring AI、RAG 與 AI Agent,完成真正能上線的企業級應用。

👉 前往 Hahow 課程頁

主題一|開發驗收期:測試不是驗證工具,是規格文件——可審查產物系列・站 3

嗨,我是凱文大叔。

上期結尾埋了一個伏筆:設計期那批「可打勾」的驗收標準,會一模一樣地出現在測試名稱裡。這期兌現。第三站:開發驗收期——全系列份量最大的一站,因為它有三層。

一句話換成三層

❌「幫我測試程式有沒有問題」——這句話的問題是,它把測試當成一個事後的動作。

但測試不該是事後的。它應該從開發第一分鐘就開始,而且結束時要留下三樣東西:

  1. 開發時就 TDD:先寫測試 → 紅 → 實作到綠 → 重構
  2. 完工後 Playwright E2E:寫成可重跑的腳本檔
  3. E2E 每步截圖 → 用這批圖產出 SOP 操作手冊

一層一層來。

3-1 開發時就 TDD:先讓我看到紅燈

為什麼要它「先寫測試」?因為 AI 寫完程式再補測試,它會寫出證明自己對的測試。測試變成實作的鏡子——你拿它驗證,等於拿它自己驗證它自己。

先寫測試,測試就是規格。

提示詞全文(可直接複製)

用 TDD 實作【功能】:
1. 先寫測試,跑一次,讓我看到它是紅的(失敗訊息貼給我)
2. 再寫最小實作讓測試轉綠
3. 最後重構,重構後測試必須維持綠

測試要涵蓋:正常路徑、邊界值、錯誤分支。
在我確認紅燈之前,不要寫實作程式碼。

最後一句約束不能少:「在我確認紅燈之前,不要寫實作程式碼。」沒有這句,它會一口氣寫完,然後告訴你「測試都過了」——你既沒看到紅燈,也不知道測試到底驗了什麼。

紅燈長這樣:

TDD 紅燈:七個測試失敗,失敗原因全部是 not implemented——為了對的理由失敗

注意失敗原因:not implemented——是功能未實作,不是打錯字。這件事要確認,否則你紅得沒有意義。紅燈證明測試真的在驗證(不是永遠會過),綠燈才證明實作滿足規格。

然後是綠燈——這是整場直播我最喜歡的一張:

TDD 綠燈:21 個測試通過,測試名稱逐條描述系統規則——前置任務未完成顯示 BLOCKED、同優先級同時間 ID 小者優先、被阻塞任務不被建議

到這裡我沒有給你們看過任何一行程式碼。但你現在已經知道這個系統的所有規則了:前置任務未完成時,任務對外顯示為 BLOCKED;同優先級同建立時間時,ID 較小者優先;被阻塞的任務不會被建議。

這就是先寫測試的真正價值——測試不是驗證工具,是規格文件。而且對照上期:這些測試名稱,正是設計期那批驗收標準的一比一翻譯。

3-2 完工後 E2E:腳本是資產

單元測試驗的是規則,E2E 驗的是「使用者真的能走完流程」。這裡有一個很容易忽略的要求:

提示詞全文(可直接複製)

用 Playwright 幫我寫 e2e 測試腳本,涵蓋這幾條使用者流程:
【流程 1】【流程 2】【流程 3】

要求:
- 寫成可重跑的腳本檔,放在 e2e/ 目錄下,加中文註解說明用途
- 不要用一次性的互動指令
- 每個腳本可獨立執行,失敗時輸出足以定位問題的訊息

重點是那兩句:「寫成可重跑的腳本檔」「不要用一次性的互動指令」。AI 很喜歡當場開瀏覽器幫你點一遍,然後跟你說「測過了,沒問題」——那個「測過」跑完就蒸發了,下次改了程式又要重來。

一次性指令跑完就沒了。腳本是資產。

之後每次改完程式,一行 npx playwright test,它自己開瀏覽器、自己填欄位、自己點按鈕、自己驗證結果。

3-3 最划算的一招:讓測試自己長出操作手冊

到這裡都還算正常。接下來這一招,是我覺得整場最划算的。

E2E 反正每一步都在操作畫面——那就順手把每一步截圖存下來:

E2E 步驟截圖軌跡牆:一次執行留下的十張截圖,完整記錄操作流程

然後:

提示詞全文(可直接複製)

在 e2e 腳本的每個關鍵步驟後加上截圖,存到 e2e/screenshots/ 並依步驟編號命名。
跑完後,用這批截圖幫我產出一份操作 SOP 文件:
每個步驟一張圖 + 操作說明 + 預期結果 + 常見錯誤。
輸出成 Markdown。

同一次執行,兩份產物:一份給機器看(回歸測試),一份給人看(SOP 操作手冊)。

自動生成的 SOP 文件頁面:步驟截圖搭配操作說明與預期結果,全文由測試執行自動產出

沒有人手寫這份文件。它是測試跑完自己長出來的。而且它永遠跟程式同步——因為它是從實際跑過的畫面生出來的。流程改了、測試會跟著改,SOP 下次重跑就自動更新。

你有多少份文件,是寫完那天就開始過期的?

站 3 收工

開發驗收期的換句話練習:一句「幫我測試」換成三層產物——TDD 讓測試先於實作(規格不是鏡子)、E2E 腳本是可重跑的資產、截圖生 SOP 讓文件永遠跟程式同步。

下期是系列完結篇:上線前。「幫我看看有沒有資安漏洞」這句話缺了什麼?為什麼全綠的稽核報告反而可疑?還有一句誠實話:AI 稽核取代不了什麼。最後把四站收成一張你可以直接存下來的總表。下期見。


主題二|事件送得出去:交易邊界與 Outbox

上期把 AI 摘要丟到背景執行緒,存檔變快了。這期要講一個在那之後才會浮現的問題。

先看這段程式——它有一個 bug,但你可能看了三遍都找不出來:

@Transactional
public void updateCustomer(Long id, CustomerForm form) {
    Customer customer = repository.findById(id).orElseThrow();
    customer.apply(form);

    publisher.publishEvent(new CustomerUpdated(customer.getId()));
}

問題是:publishEvent 這一行執行的時候,交易還沒有 commit。

@Transactional 的 commit 發生在方法返回之後。所以事件送出去的那一刻,客戶資料還只存在於這個交易裡,資料庫外面的世界看不到它。

平常這沒事,因為交易通常都會成功。但只要有一次沒成功——

一次 rollback 就穿幫

我實際跑了一次:讓交易在 publishEvent 之後拋例外、rollback,然後看監聽器有沒有跑。

T1 交易 rollback 後,一般 @EventListener 竟然已經執行過了
   監聽器執行=true / 資料庫實際內容=原本的名字

監聽器執行了。資料庫裡卻還是「原本的名字」——那筆更新根本沒存進去。

上期我們已經讓摘要在背景執行緒跑了,所以這裡發生的是:AI 對著一筆根本不存在的更新,認真地產生了一份摘要,然後把它存起來。

使用者看到的畫面是:客戶資料沒變(存檔失敗了),但摘要換成了新的、描述著一個從未存在過的版本。

這種 bug 最麻煩的地方在於——它平常不會出現。你的測試會過、開發環境會過、上線頭三個月都不會有事。它只在 rollback 的那一次出手,而那通常發生在半夜、發生在你查不到的地方。

事件送出時機的三種結果:① 用 @EventListener 遇上 rollback,摘要已經開始跑,資料卻退回原狀;② 改用 @TransactionalEventListener 遇上 rollback,事件掛起等 commit、最後直接丟棄,摘要不會產生;③ 用 @TransactionalEventListener 但 commit 成功後當機,資料在、事件消失且沒有任何紀錄——Outbox 補的就是第三種