Kiro SDD 入門(二):用 PBT 抓出你測不到的 bug
你寫的測試只能覆蓋你想得到的案例,PBT 幫你撞出你想不到的。這篇用 Kiro 官方的 __proto__ 安全洞案例,講清楚 PBT 在 Spec workflow 裡怎麼運作。
目錄
系列文章地圖
點節點可以直接跳到那一篇。
上一篇講了 Specs 的三階段,這篇要接一個問題:AI 照著你的 requirements 生了 code,你怎麼知道它真的對?
上一篇:Spec 基礎
先講一般測試的問題
假設你做了一個「儲存 API key」的功能,你會怎麼測?
- 存一個 key 叫
"openai",值是"sk-abc123",取出來看對不對 - 再存一個
"anthropic",值是"sk-xyz789",取出來看對不對
測完兩三個案例,看起來都過了,你就覺得沒問題了。
但你測的都是「你想得到的正常輸入」。如果有人存了一個 provider name 叫 "__proto__" 呢?你不會想到要測這個,AI 寫測試也不會想到。
PBT 的做法:讓電腦幫你想案例
PBT(Property-Based Testing)換了一個思路,你不用自己想案例,你只要告訴框架「什麼規則應該永遠成立」,框架會自動生成大量隨機輸入去測這條規則。
以剛才的 API key 功能來說,規則就是:
不管 provider name 跟 API key 是什麼字串,存進去再取出來,值應該要一樣。
框架會自動生成各種你想不到的字串去測,像空字串、超長字串、特殊字元和 unicode,也包括 "__proto__" 這種在 JavaScript 物件裡有特殊意義的屬性名稱。一次跑個 100 組,看有沒有哪一組會讓這條規則不成立。
真實案例:第 75 次就抓到安全問題
這是 Kiro 官方 blog 記錄的真實案例。
背景
在做一個 chat app,需要存各家 LLM provider 的 API key。Kiro 照著 Spec workflow 走,從 requirements 推出了一條規則(property):「存進去再取出來要一樣」,然後用 fast-check 這個框架去跑 PBT。
程式碼大概長這樣:
fc.assert(
fc.property(fc.string(), fc.string(), async (provider, apiKey) => {
saveApiKey(provider, apiKey);
const retrieved = loadApiKey(provider);
return retrieved === apiKey;
}),
{ numRuns: 100 },
);
fc.string() 會隨機生成字串,numRuns: 100 就是跑 100 組。
跑到第 75 次,fail 了
框架隨機生成了 provider = "__proto__",結果存進去之後取出來的值不對,測試 fail。
框架接著做了一件很有用的事
fail 之後,框架會先自動「簡化」那組亂七八糟的隨機輸入,再交給你。
這個過程叫 shrinking,概念跟你 debug 的時候一樣:你會試著把問題簡化到最小重現步驟,把不相關的東西一個一個拿掉,直到找出最核心的觸發條件。框架自動幫你做這件事。
簡化完之後,最小的失敗案例只剩這兩個值。
- provider:
"__proto__" - apiKey:
" "(一個空白)
手寫案例只驗證你想得到的輸入;PBT 用大量隨機輸入去撞規則,撞到失敗後再用 shrinking 把失敗案例縮到最小。
apiKey 被簡化到只剩一個空白,代表問題跟 value 無關,純粹是 "__proto__" 這個 key 造成的。
為什麼 "__proto__" 會出問題
JavaScript 的物件有一個特殊機制:每個物件都有一個隱藏的 __proto__ 屬性,指向它的原型(prototype)。這是 JavaScript 繼承系統的核心。
所以當你把 "__proto__" 當成一般的 key 去寫入:
obj['__proto__'] = 'some value';
JavaScript 會把這次寫入當成是想改這個物件的原型,不會當普通資料存起來。結果寫入看起來成功了,讀取的時候拿到的卻是原本的 prototype 物件,不是你存的值。
這就是為什麼「存進去再取出來」這條規則被打破了。
修法
用 Object.create(null) 建立一個「沒有原型」的乾淨物件:
const apiKeys = Object.create(null);
一般的 {} 會繼承 Object.prototype,所以 __proto__ 有特殊意義。但 Object.create(null) 建出來的物件沒有原型鏈,__proto__ 就變成一個普通的 key,可以正常存取。
這種 bug 你手動測不會想到,AI 寫測試也不會想到。但 fast-check 的隨機生成器裡面內建了 "__proto__" 這類常見的危險字串(是社群多年累積貢獻的),所以 PBT 會撞到。
PBT 在 Kiro 裡怎麼用
Kiro 把 PBT 整合進 Specs workflow,從 EARS 需求抽出 property,跟著 tasks 產生 PBT,失敗時把最小失敗案例交回給你決定改哪裡。
- 你用 EARS 寫 requirements(上一篇講的)
- Kiro 在 design 階段自動從 requirements 抽出可以測的規則(properties)
- 生成 tasks 的時候會包含 PBT 測試
- PBT 預設是 optional,你可以先跑核心實作,確認基本功能沒問題再開 PBT
- 如果 PBT fail,Kiro 會顯示簡化後的最小失敗案例,你可以決定要改 code、改 test、還是改 requirement
PBT 跟 unit test 的關係
這兩個搭配用效果最好。
- unit test:鎖定特定案例,確保已知的 bug 不會再出現
- PBT:用大量隨機輸入去撞你沒想到的邊界
numRuns 預設 100,可以自己調。跑越多信心越高但越花時間,看你的需求。
參考
https://kiro.dev/docs/specs/correctness/https://kiro.dev/blog/property-based-testing-fixed-security-bug/https://kiro.dev/docs/specs/bugfix-specs/
支持作者 ☕
透過 LINE Pay 支持
透過 Ko-fi 支持
相關文章
企業 AI 客服實戰:從掃描紙本單據到自動回答的完整記錄(2026)
企業 AI 客服系統實戰踩坑記錄 | 飯店宴會部用 AI 自動辨識紙本訂單,搭配混合搜尋做自然語言查詢。從開源模型冷啟動 73 秒的教訓到系統優化省下一半費用,完整經驗分享。
Anthropic Harness 設計:讓 AI Agent 連跑 6 小時不失控的架構拆解(2026)
Anthropic 官方拆解長時間 AI Agent 的兩大失敗模式(context anxiety 與自我評價偏差),並用三代理架構 Planner / Generator / Evaluator 搭配 Sprint Contract 解決。含成本數據、實戰案例和可直接套用的設計原則。
2026 Obsidian AI 外掛推薦:從搜尋、寫作到記憶系統的 5 大神器
Obsidian 裝了幾百篇筆記卻找不到?本文推薦 5 款必裝的 Obsidian AI 外掛,涵蓋語意搜尋、寫作輔助,以及結合 FSRS 的 EngramQuest 記憶系統。
留言討論
Facebook 留言