iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0
自我挑戰組

踏入品質保證工程之路:QA 新手如何善用 AI 與開發者工具系列 第 23 篇

偵測、隔離、決定:用值得信任的 CI pipeline 修正不穩定測試(flaky tests)

  • 分享至 

  • xImage
  •  

品質關卡寫好之後,下一個實務問題是它們應該在什麼時候執行。所有測試都跑在每次 push 上,聽起來很嚴謹——直到開發者開始在漫長的 pipeline 中苦等,並開始尋找繞過它的方法。而如果把所有東西都推到發佈前才檢查,問題會很晚才出現,屆時修復的成本更高,而圍繞這次變更的脈絡可能早已消失。目標並不是把每個測試都跑得越頻繁越好,而是把每一項檢查放在它的結果最有用的位置。

同樣的原則也適用於 flaky 測試。一個紅、綠、紅反覆變色的測試,不只是製造一個惱人的失敗——它損害的是對整個 pipeline 的信任。假綠燈在測試其實沒問題時說:「測試沒問題」;而 flaky 測試在什麼都沒壞時說:「有東西壞了」。兩個方向都在教團隊不信任訊號。實務上的解法,是讓 CI/CD pipeline 對什麼時候跑什麼有所取捨,同時替不穩定的測試留下一條受控制的路徑:從偵測、到隔離、最後做出決定。

1. 分層:快的檢查往前放,慢的檢查往後放

基本規則很簡單:一項檢查越快、越便宜、越可重複,它就應該跑得越頻繁。 靜態分析與單元測試通常外部依賴很少、很快可以完成,所以應該放在 pipeline 前段。在每次 push 上執行它們,開發者就能在變更記憶還新鮮時得到回饋。契約(contract)與整合檢查可以放後面一點,因為它們通常牽涉更多元件。更廣泛的 AI 評估可以再往後移,因為它們花費更長的時間,也可能消耗額外的資源。

舉例來說,如果開發者改了一個 API 回應欄位,卻要等 40 分鐘才讓評估套件發現一個幾秒鐘就能抓到的基本契約失敗,這幾乎沒有價值。一個有用的 pipeline 因此可能從靜態檢查與單元測試,走到契約與整合檢查,再到更廣泛的評估與營運檢查。重點不在於層數的多寡,而在於每一層的失敗都對應到一個明確的下一步動作。單元測試失敗,應該把開發者指回那個變更;契約測試失敗,應該指向介面本身;評估失敗,應該擋下或標記發佈決策,而不是變成又一則沒有人知道該怎麼解讀的通知。

這種分層也讓 CI/CD 更容易落地。以 CircleCI 為例,workflow 可以定義哪些作業跑在 push、pull request、排程執行,或是後面的部署階段。pipeline 於是成為一連串逐漸昂貴的證據,而不是一個巨大的測試水桶。這件事之所以重要,是因為快速回饋會改變開發者的行為:如果一個便宜的檢查能及早抓到問題,團隊就比較沒有動機只因為「pipeline 太久」而繞過它。

2. Flaky 測試:先偵測,隔離,然後再決定

面對 flaky 測試的第一步是偵測,而不是刪除。把同一個案例、貼著同一個 commit 反覆執行,並記錄它的結果是否一致。如果它這次過、下次失敗,而程式碼與環境都沒有相關變更,這種不一致就是值得留下的證據。我不希望結論建立在記憶上——「這個測試本來就很不穩」——因為那會把工程問題變成鄉野傳說。pipeline 應該留下痕跡:案例、commit、執行次序、結果,以及不一致的模式。

一旦確認某個測試不穩定,第二步是隔離(quarantine)。測試可以繼續執行並回報結果,但暫時被移出阻擋路徑,讓一個不可靠的訊號不再擋住別人的工作。關鍵詞是暫時。隔離應該有負責人、有原因、有到期日。如果一個測試可以永遠處於隔離狀態,那 quarantine 就只是「停用這個測試」的客氣說法。

第三步才是決定:修好它、刪掉它,或把它改寫成穩定的版本。 依賴時序、共享狀態、不穩定外部服務或非確定性 AI 輸出的測試,可能需要重新設計,而不是反覆重試。當風險已經在其他地方被涵蓋、或測試已不再代表有效行為時,刪掉它是合理的;當底層的產品行為很重要、而測試正是要保護它時,修好它更好。目標不是不惜任何代價讓 pipeline 變綠,而是讓綠燈重新值得信任。

3. AI 測試放進 CI 的三個難題

第一個難題是非確定性。傳統軟體測試往往假設相同輸入會產出完全相同的輸出,但 AI 系統面對同一個 prompt 與同一份上下文,可能產出不同的措辭——有時甚至是意思上不同的回應。這意味著 response == "Your order will arrive tomorrow" 這種斷言可能太脆弱。AI 測試因此可能需要規則或範圍:答案必須指出正確的送達日期、不得宣告未經授權的操作、必須包含必要的政策條件,並且維持在可接受的品質範圍內。

第二個難題是成本。每一次評估執行都可能消耗計費的模型用量,所以把整套評估集放在每次 push 上,可能讓 pipeline 變得不必要地昂貴。但這不代表成本檢查應該被默默跳過。pipeline 應該有明確的預算或上限,而超出上限應該依關卡的規則,產生看得見的失敗或阻擋條件。

第三個難題是時間。完整的黃金集(golden set)或對抗性(adversarial)評估,動輒要跑上數十分鐘,尤其是在牽涉多次模型呼叫時。答案是讓較小的抽樣樣本與完整評估分開,而不是假裝兩者做的是同一件事。

第一個 fragment 展示的是各階段該放在哪裡。下面的範例則展示:前面談的 flaky 處理順序,如何轉成 pipeline 設定——偵測從不阻擋、隔離只回報不設關、到期日強迫做出決定。再次強調,詞彙僅為示意:

pipeline:
  - stage: flake-detect     # 觸發條件:每次 push,並固定貼著同一個 commit
    run: [record-case-hashes]   # 每次執行記下案例 -> 輸出雜湊;commit 沒變而雜湊變了,就是一個警訊
    budget: 3 min
    on_fail: none           # 偵測永遠不阻擋;它只負責餵隔離清單

  - stage: quarantined      # 觸發條件:每次 push;內容為一份明列的清單,每筆都帶有日期
    run: [quarantine-suite]
    budget: 10 min
    on_fail: report_only    # 結果照樣收集、照樣可見,但不拿它們關任何人
    expire: 14 days         # 超過日期的條目,自動讓這個階段失敗

  - stage: unit             # 觸發條件:每次 push
    run: [unit-tests]
    retry: 1                # 最多重跑一次,且只限基礎設施類的失敗,斷言失敗絕不重跑
    on_retry_fail: block    # 重跑一次之後,紅就是紅

  - stage: quarantine-audit # 觸發條件:每晚
    run: [list-expired-entries]
    budget: 2 min
    on_fail: block_release  # 不該有任何筆還留在清單上超過日期:發佈前先修好、刪掉或改寫

確切的設定會依團隊的 CircleCI 配置而不同,但重要的是原則:快的檢查保護每一次變更;昂貴的 AI 評估以較慢的節奏提供更廣的證據。 一小份 AI 樣本可以及早抓住明顯的迴歸,而完整的黃金集與對抗性套件可以按排程或在發佈邊界上執行。這就在回饋速度、評估涵蓋範圍與模型成本之間,取得了一個務實的折衷。

CI 驗證 pipeline 筆記

階段 執行內容 時間預算 觸發條件 失敗時的動作
靜態 Lint、型別檢查 2 分鐘內 每次 push 阻擋
單元 單元測試、突變抽樣 6 分鐘內 每次 push 阻擋
契約與整合 契約測試、整合煙霧測試 15 分鐘內 每個 pull request 阻擋
評估 黃金集抽樣、對抗性案例 40 分鐘內 每晚或標籤 阻擋發佈、通知負責人
成本與延遲 成本與延遲分佈檢查 10 分鐘內 每晚 通知負責人
隔離 疑似 flaky 的案例,僅收集結果 不計入預算 每次 push 不阻擋;到期時審查

這張表把分層策略變得具體:為每個驗證階段指定時間預算、觸發條件,以及失敗時的動作。靜態檢查與單元測試跑在每次 push 上,失敗就阻擋——這很合理,因為它們相對快、相對便宜。契約與整合檢查跑在每個 pull request 上,而較廣的評估層每晚執行或在明確觸發時執行,並且可以擋下發佈。成本與延遲檢查同樣每晚執行,但只通知負責人而不是自動阻擋,說明了並非每一項重要的量測都必須是硬關卡。最後,疑似 flaky 的案例被放進隔離:它們繼續收集結果,但在到期審查之前,不計入阻擋用的預算。

這張表也說明了為什麼觸發條件與失敗動作必須寫在同一份規格裡。「跑 AI 評估」這句話是不完整的,除非團隊知道它什麼時候跑、失敗時會發生什麼。同樣地,「隔離 flaky 測試」也是不完整的,除非測試持續回報、而且有一個明確的時間點,讓人必須決定它的未來。這樣做出來的 pipeline,每一項檢查都有它的目的,而不是一堆剛好被執行到的作業。

Pipeline 是分層的,不是即時的

這些時間預算是演練目標,不是實測結果;真實數字會隨環境、工作負載、依賴與模型版本而變動。分層也犧牲了一些即時性:只在夜裡跑的評估,代表問題可能到隔天才浮現。隔離也類似:它在降低阻擋噪音的同時,暫時削弱了那個測試提供的保護。因此目標不是把每項檢查推得越早、越頻繁越好,而是把每一項放在它的訊號值得付出成本與延遲的位置。快的檢查保護開發者的下一步,慢的檢查保護發佈本身,而 flaky 測試永遠不該被容許悄悄重新定義「綠燈」的意義。


上一篇
如果人人都能通行,就別稱之為關卡:讓 CI/CD 成為品質關卡
下一篇
別再亂點了:有目的的探索性測試(Exploratory Testing)
系列文
踏入品質保證工程之路:QA 新手如何善用 AI 與開發者工具 共 24 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言