Skip to content

第 29 天|這支測試怎麼有時通過、有時失敗? ​

前言 ​

昨天,我們順著失敗紀錄查原因。但你可能也遇過另一種情況:程式沒改,這次測試失敗,重跑一次卻通過了。

這時候很容易想,既然過了,就先這樣吧。只是原因還沒找到,下次它可能又挑發布前回來。今天我們先固定條件,多跑幾次,看看它到底失敗得多頻繁。

開始之前 ​

同樣條件下,有時通過、有時失敗的測試,通常叫 flaky。今天用 flaky-detect 來量:程式和環境先固定,事先講好跑幾次,再記下其中失敗幾次。

因為我們要看每次真正的結果,配套專案設了 retries: 0,不在失敗後自動重試。如果只盯著重試後的綠燈,就容易漏看前面失敗的那次。

這次拿 tests/flaky/render-race.spec.ts 練習。它故意在搜尋後等一段固定時間,就直接讀取結果,等待多久由 FLAKY_WAIT_MS 決定。換句話說,它在賭「等這麼久,應該好了吧」。這是拿來示範問題的寫法,別帶回去當團隊範本。

動手試試 ​

接著,我們在 sdet-skills/ 開啟助手對話,把這次要量的條件交代清楚:

text
請用 flaky-detect 量測 tests/flaky/render-race.spec.ts。
這次固定 FLAKY_WAIT_MS=1350、重複 20 次,不自動重試,也先不要修測試。
保留每次結果與失敗訊息,回報失敗幾次、總共幾次,以及疑似原因。
本輪證據另存到 output/sessions/20260922_flaky-1350/,避免下輪覆蓋。

最後的目錄是這輪練習要存證據的位置,你可以換成自己的日期和名稱。這個例子指定跑 20 次;平常就先讀專案設定,把次數定下來。跑完才看結果,不要看到不喜歡的數字又偷偷加跑。

想自己在終端機試,也可以執行這一行:

bash
FLAKY_WAIT_MS=1350 npm run test:flaky

這個指令已經設好 --repeat-each=20,本機預設同時跑四個工作。跑完後,報告在 output/reports/playwright/,結果檔在 output/runs/playwright-results.json,失敗證據在 output/runs/playwright-artifacts/。

先把這些檔案和執行參數一起存進本輪目錄,再開始下一輪。因為預設輸出位置相同,下一輪可能會覆寫前一輪的資料。

跑完之後 ​

當時,我們試了幾個不同的等待時間,每輪都跑 20 次。1350 和 1400 毫秒各多跑一輪,所以這兩列有兩份結果;其他列的「—」代表沒有第二輪紀錄:

等待時間(毫秒)第一輪失敗次數/20第二輪失敗次數/20這輪怎麼看
70020/20(100%)—全部失敗,先查持續失敗原因
100020/20(100%)—全部失敗,先查持續失敗原因
120018/20(90%)—有紅有綠,失敗比例很高
130018/20(90%)—有紅有綠,失敗比例很高
1350(預設值)7/20(35%)12/20(60%)兩輪差 25 個百分點
14002/20(10%)5/20(25%)兩輪差 15 個百分點

先看前面幾列。等 700 毫秒,這輪 20 次全部失敗,就先交昨天的流程查原因。等 1200 或 1300 毫秒,雖然各失敗 18 次,還是有兩次通過,也就已經觀察到時好時壞。

再往下看,同樣等 1350 毫秒,第一輪失敗 7 次,第二輪卻失敗 12 次。設定沒變,量到的比例還是會不同。

所以交報告時,我們最好說「20 次裡失敗 7 次」,把總次數一起寫出來。只說失敗率 35%,別人看不出你量了多少,也很難判斷這個數字能相信到哪裡。

次數記好了,助手還得把疑似原因和下一步寫給接手的人。下面用第一輪 1350 毫秒的數字示範格式,並非當時原始紀錄的逐字節錄:

yaml
nodeid: "tests/flaky/render-race.spec.ts > 搜尋結果應該在送出後立刻可讀"
runs: 20
failures: 7
flake_rate: 7/20
suspected_root_cause: wait-condition
classification: wait-timing
confidence: medium
next_step: "改成等待搜尋結果符合條件,再讀取並核對內容"

這裡先把等待條件列為疑似原因,交給後續修復。你可能會想,那就再多等一點?但等久了比較少失敗,仍沒回答「畫面到什麼狀態才可以讀值」,這件事還要繼續查。

順帶一提,tests/README.md 還記了一個插曲。另一批 tests/broken/ 的壞測試,原本綁死某段顯示文字,結果自己也時紅時綠。連示範「一定會壞」都有失手的時候。後來改成尋找不存在的 CSS 類別,才固定失敗。

背後怎麼做 ​

回到今天的量測。次數跑滿後,我們就按這輪看到的結果,決定下一步:

結果下一步
有失敗,也有通過記下比例、疑似原因,交對應流程處理
全部失敗本輪未觀察到時紅時綠,先走第 28 天的失敗分析
全部通過記為本輪未重現(not-reproduced),結束這輪量測

這張表只處理眼前這一輪。20 次全過,只能說這次沒重現;20 次全失敗,也不能保證它以後永遠不會過。之後有新證據,可以再量,但前一輪的結果要留著。

如果確實有時好時壞,接手的人還需要知道從哪查。助手會先記下疑似原因,大致分成下面幾種:

疑似原因欄位調查方向
wait-condition元素出現了,但還不能操作或讀值
data-pollution其他測試留下資料或狀態
parallel-race同時搶用帳號或同一筆資料
external-dependency受外部服務、時間、時區或亂數影響
render-timing動畫、非同步更新或重繪的時機不同

這些名稱用來說明「懷疑哪裡出了問題」。另外還有 classification,要沿用昨天的分類,讓下一個流程知道怎麼接。所以上面同時寫了 wait-condition 和 wait-timing,分別是疑似原因與交接分類。

這也是為什麼每次失敗訊息都要留。如果一次是逾時,另一次是帳號被鎖,可能有兩個問題,需要分開查,不能用一個失敗比例就帶過。

查到需要改測試,就交 test-heal;改完再請 re-run-gate 照設定重跑驗收。若要暫時把這支測試移出會擋住發布的檢查,再由 flaky-manager 管理隔離和恢復。今天先把量測做好,這些後續動作還沒有實際執行。

今天學到什麼 ​

現在再說這支測試不穩,我們就有數字可以拿出來:跑了幾次、失敗幾次,每次錯在哪裡。同一個 1350 毫秒設定,兩輪結果仍有差距,所以回報時要把樣本數和證據一起帶上。

走到這裡,找問題、修問題、照顧測試,各自都有做法了。明天是最後一天,我們把它們接成一輪值班,試試看這位新同事能接著做完多少。


作者備忘錄 — 正式出版前應移除

章節大綱

  1. 前言 — 把偶爾失敗變成可量測的問題。
  2. 開始之前 — 固定條件、關閉自動重試,說明故意寫壞的示範。
  3. 動手試試 — 提示詞、執行指令與每輪證據保存位置。
  4. 跑完之後 — 保留兩輪數字,以第一輪示範交接格式。
  5. 背後怎麼做 — 三種分流、疑似原因與分類欄位,交代後續流程。
  6. 今天學到什麼 — 帶著分母回報,接到值班編排。

參考資料 ​

  1. Qingzhou Luo、Farah Hariri、Lamyaa Eloussi、Darko Marinov(2014)《An Empirical Analysis of Flaky Tests》,FSE 2014 - 大規模分析 flaky 測試的成因分布,本篇五類根因的來源
  2. John Micco(2016)《Flaky Tests at Google and How We Mitigate Them》,Google Testing Blog - 工業規模下的 flaky 治理:偵測、隔離、解除,以及為什麼不能靠重跑
  3. Martin Fowler(2011)《Eradicating Non-Determinism in Tests》 - 不確定的測試會讓整套測試失去意義,以及隔離為什麼是止血不是解法
  4. Edwin B. Wilson(1927)《Probable Inference, the Law of Succession, and Statistical Inference》,Journal of the American Statistical Association 22(158) - 比例的信賴區間:小樣本下的重現率誤差有多大,正是「N 夠不夠大」的計算依據
  5. 本專案 skills/maintain/flaky-detect/SKILL.md(判準與五類根因)、tests/flaky/render-race.spec.ts(掃描實測)、tests/README.md 的 flaky 那一節、references/green-cheating.md - 判準、五類根因與掃描實測的真檔