為什麼生成式 AI 測試會讓慣用的斷言失效

斷言回應必須等於某個固定字串,第二次執行就會失敗,而原因跟你的產品一點關係也沒有。把條件放寬成只檢查子字串,那些細微出錯的輸出又會照樣通過。兩種做法都沒有用,而在這兩者之間來回調整,正是許多團隊最後認定「生成式功能無法測試」的過程。

那該斷言什麼

屬性JSON 格式正確、必要欄位齊全、長度在限制內、引用的來源確實存在。這些都能檢查,而且在多次執行之間保持穩定。
拒答該拒絕的就拒絕,而且每次都一致。這往往是整套測試中價值最高的一項檢查。
依據性每一項陳述都能追溯到所提供脈絡中的內容。這能揪出憑空捏造,又不必去評斷文字風格。

要看比率,不是結果

跑一次什麼都說明不了。用相同的輸入執行很多次,追蹤該屬性成立的頻率有多高。你的衡量單位是比率,而你該留意的訊號是比率的變化,不是某一次的失敗。

這也改變了「退化」的定義。問題從來不是「它夠完美嗎」,而是「這次改動有沒有讓它比上週更差」。

挑選要斷言的屬性

困難的地方不在於做法本身,而在於當你無法檢查答案時,要決定檢查什麼。一個好用的切入點是:去問什麼會讓輸出變得不能用,而不是問什麼會讓它變好。

「不能用」是具體的:呼叫端無法解析的格式錯誤輸出、引用了根本不存在的來源、本來該給答案卻拒答、本來該拒答卻給了答案、內容超出下游的限制。這些都是二元的、可檢查的,而且每一項對應的都是真實事故,而不是對品質的主觀看法。

相對地,「好不好」是一種因人而異的判斷,不值得寫成一道關卡。你想追蹤它也可以,抽樣交由人工審查,但別把它放進流程裡——放進去只會產生爭論,而不是決定。

可以照常測試的部分

一個含有生成式功能的產品,大部分仍然是普通的確定性軟體:身分驗證、儲存、流量限制,以及包在該功能外圍的介面。這些都應該用傳統方式測試;只因為其中一部分是非確定性的,就把整個產品都當成非確定性來看待,會讓你白白損失大量低成本的覆蓋率。

把界線明確畫出來。界線之內,用屬性和比率。界線之外,對執行中的應用程式做一般的斷言。

終端機

npm install -g @testsprite/testsprite-cli
testsprite setup

如果你不想安裝任何東西,儀表板也能做到同樣的事。命令列能做的其他所有事情,都收錄在 CLI 儲存庫。

有兩條路可走,而這個選擇其實取決於誰負責這條流程。從儀表板連接 GitHub App 完全不需要改動你的儲存庫,因為它是對你的建置流程已經發出的部署做出反應。而加上一個 GitHub Actions 步驟,則是把這道檢查放進儲存庫裡,讓它像建置流程的其他部分一樣受到審查。

TestSprite 在這項功能周邊涵蓋了什麼

產品中確定性的那大部分,對多數團隊來說,比重遠遠更大。身分驗證、儲存、流量限制、包在功能外圍的介面,以及使用者實際會走的每一條流程,都是普通軟體,照傳統方式測試即可。Auto-Authentication 讓工作階段在整次執行中保持有效,Dynamic Variables 在呼叫之間傳遞值,Dependency Chains 推導出執行順序,Auto-Cleanup 則精準清除這次執行所建立的東西。

測試涵蓋範圍由你的產品自動產生,再用白話描述加以調整;每次變更都會對已部署的應用程式執行,讓系統中能夠精確檢查的部分,就被精確地檢查。

生成式功能本身需要的是屬性和比率,那是另一套測試框架,有自己對「通過」的定義。把這條界線明確畫出來,才能避免團隊走向兩種極端:對確定性的程式碼硬套統計方法,或因為精確比對老是失敗而乾脆放棄斷言。

可以用 AI 來為輸出評分嗎?

這很常見,也行得通,但有一點要注意:負責評分的模型本身也有自己的失效模式,需要自己的校準。在信任它之前,先抽樣拿它跟人工判斷對照。

要跑幾次,比率才有意義?

跑到再增加次數、數字也不再變動為止。從幾十次開始,而不是幾百次,然後觀察是否穩定。

生成式測試應該擋下合併嗎?

拒答率大幅下滑,應該擋。品質上的小幅波動,不該擋,否則整條流程只會變成雜訊。

那成本呢?

反覆執行是要花錢的。每次變更時用抽樣,而不是跑完整套,再另外定期排程執行完整的一套。

提示詞測試算是同一回事嗎?

有關聯。提示詞的變動是比率改變最常見的原因,這也正好說明了為什麼應該把提示詞當成有版本控管的產出物來管理。

簡短版

看屬性和比率,而不是相等。

生成式 AI 測試以屬性、拒答和依據性取代精確斷言,並以多次執行的比率來衡量。產品中確定性的那大部分照常測試,並把界線明確畫出來。