昨天,DevPulse 的 patchFlow 已經會把 Gemini 產生的補丁套用到原始碼,並將結果寫進 fixed/。三個範例分別是 async-await.cjs、sql.cjs 和 boundary.cjs。
但「補丁成功寫進檔案」不等於「Bug 已經修好」。今天先不增加新功能,而是把 Jest 的測試目標切換到 fixed/,用原本的測試條件驗收 AI 產生的修復版本。
今天做三件事:
samples/ 或 fixed/。Day 22 準備了三個故意寫錯的案例:
| 範例 | 原本要驗證的問題 | 測試預期 |
|---|---|---|
async-await.cjs |
forEach() 不會等待裡面的 async callback |
所有工作完成後,回傳完整結果陣列 |
sql.cjs |
把使用者輸入直接拼進 SQL 字串 | SQL 使用 ? 佔位符,資料另外放進參數陣列 |
boundary.cjs |
profile 不存在時直接存取 name 會報錯 |
缺少 profile 時回傳 Unknown |
這些預期行為就是驗收標準。今天不能因為 AI 改不過,就把 expect() 改成符合錯誤結果;否則只是讓測試配合 Bug,而不是讓程式符合需求。
samples/ 與 fixed/ 之間切換目前三個測試檔都直接匯入 samples/。如果要比較原始碼與修復版,最簡單的方法是讓匯入路徑由環境變數決定:預設測試原始檔,只有明確設定 DEVPULSE_SOURCE_DIR=fixed 時,才改測 fixed/。
以 tests/async-await.test.cjs 為例,把原本的匯入:
const { sendAll } = require("../samples/async-await.cjs");
改成:
const sourceDir = process.env.DEVPULSE_SOURCE_DIR || "samples";
const { sendAll } = require(`../${sourceDir}/async-await.cjs`);
tests/sql.test.cjs 也採用同一個方式:
const sourceDir = process.env.DEVPULSE_SOURCE_DIR || "samples";
const { findUser } = require(`../${sourceDir}/sql.cjs`);
tests/boundary.test.cjs 則是:
const sourceDir = process.env.DEVPULSE_SOURCE_DIR || "samples";
const { getUserName } = require(`../${sourceDir}/boundary.cjs`);
只修改匯入路徑,保留原本的測試內容與 expect()。 這樣兩次測試使用的是同一套驗收條件,唯一差別是載入的程式碼來自 samples/ 還是 fixed/。
專案目錄會像這樣:
devpulse/
├─ samples/
│ ├─ async-await.cjs
│ ├─ sql.cjs
│ └─ boundary.cjs
├─ fixed/
│ ├─ async-await.cjs
│ ├─ sql.cjs
│ └─ boundary.cjs
└─ tests/
├─ async-await.test.cjs
├─ sql.test.cjs
└─ boundary.test.cjs
執行前先確認 Day 24 的 patchFlow 已經為三個案例產生對應的修復檔。如果 fixed/ 裡少了某個檔案,Jest 會因為找不到模組而失敗;這和測試發現程式行為不符合預期,是兩種不同的問題。
先在終端機執行:
npm test
因為沒有設定 DEVPULSE_SOURCE_DIR,測試預設載入 samples/。這三個檔案本來就是刻意保留的錯誤案例,所以預期會看到三個測試套件失敗。
FAIL tests/async-await.test.cjs
FAIL tests/sql.test.cjs
FAIL tests/boundary.test.cjs
Test Suites: 3 failed, 3 total
Tests: 3 failed, 3 total
以上是輸出示意,實際格式與錯誤細節會依 Jest 版本及測試內容而略有不同。發文時請換成自己的終端機截圖,並確認畫面確實顯示三個原始案例都失敗。
先看到紅燈反而是好事:它證明測試有能力抓出我們預先定義的問題。接下來才讓同一組測試去檢查 AI 的修復結果。
fixed/:看看 AI 有沒有真的修好如果使用 Windows PowerShell,設定環境變數後再執行測試:
$env:DEVPULSE_SOURCE_DIR = "fixed"
npm test
如果使用 macOS 或 Linux 的終端機,可以執行:
DEVPULSE_SOURCE_DIR=fixed npm test
這次測試載入的是 fixed/ 裡由 patchFlow 產生的版本。若三個補丁都符合原本的測試契約,預期結果會變成:
PASS tests/async-await.test.cjs
PASS tests/sql.test.cjs
PASS tests/boundary.test.cjs
Test Suites: 3 passed, 3 total
Tests: 3 passed, 3 total
這同樣是預期輸出示意,並不是預先保證 Gemini 每次都會產生正確補丁。請以實際執行結果為準:如果有案例仍然是 FAIL,那就是今天最值得記錄的結果,不要為了得到綠燈而更改測試預期。
三個測試分別在確認不同的行為:
sendAll() 必須等工作完成,再回傳完整的結果。可使用正確的 for...of 與 await,或以 Promise.all() 等待全部工作完成。findUser() 應呼叫 db.query(sql, [userName]),不能只把 ? 放進 SQL 字串,卻忘了傳入參數陣列。getUserName({}) 應依照測試契約回傳 Unknown,而不是因為 profile 不存在就拋出例外。AI 產生的補丁不一定每次都正確。若 fixed/ 仍有測試失敗,先看 Jest 指出的 Expected 與 Received,再檢查修復檔實際改了什麼。
例如,要比較 SQL 案例,可以執行:
git diff --no-index samples/sql.cjs fixed/sql.cjs
若要先檢查 JavaScript 語法,可以執行:
node --check fixed/sql.cjs
node --check 只能協助檢查語法,不能證明邏輯正確;真正的行為驗收仍然要靠 Jest。如果測試指出 SQL 的第二個參數遺漏,就回頭檢查 fixed/sql.cjs,而不是把測試改成只檢查 SQL 字串。
另外,若 PowerShell 已經設定過環境變數,之後想回到原始案例,可以執行:
Remove-Item Env:DEVPULSE_SOURCE_DIR
npm test
這會移除目前終端機的設定,讓測試再次預設載入 samples/。
今天最重要的觀念是:測試要驗收程式,不是替程式找藉口。 Gemini 能分析問題,也能產生補丁,但它無法單靠「成功輸出檔案」證明修復正確。把測試固定下來,才有辦法客觀比較修復前後的差異。
如果三個案例都從 FAIL 變成 PASS,代表這三項已定義的行為符合預期;但這不代表整個專案沒有其他 Bug,也不等於完成完整的安全性稽核。測試能證明的範圍,取決於我們實際寫下的驗收條件。
下一篇 Day 26,會把目前的分析流程包裝成更方便的本機 CLI,讓 DevPulse 朝「用一行指令啟動分析」再前進一步。
DEVPULSE_SOURCE_DIR 切換測試目標samples/ 重現三個紅燈fixed/ 裡的修復版本git diff 檢查未通過的補丁