Appearance
第 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 | 這輪怎麼看 |
|---|---|---|---|
| 700 | 20/20(100%) | — | 全部失敗,先查持續失敗原因 |
| 1000 | 20/20(100%) | — | 全部失敗,先查持續失敗原因 |
| 1200 | 18/20(90%) | — | 有紅有綠,失敗比例很高 |
| 1300 | 18/20(90%) | — | 有紅有綠,失敗比例很高 |
| 1350(預設值) | 7/20(35%) | 12/20(60%) | 兩輪差 25 個百分點 |
| 1400 | 2/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 毫秒設定,兩輪結果仍有差距,所以回報時要把樣本數和證據一起帶上。
走到這裡,找問題、修問題、照顧測試,各自都有做法了。明天是最後一天,我們把它們接成一輪值班,試試看這位新同事能接著做完多少。
作者備忘錄 — 正式出版前應移除
章節大綱
- 前言 — 把偶爾失敗變成可量測的問題。
- 開始之前 — 固定條件、關閉自動重試,說明故意寫壞的示範。
- 動手試試 — 提示詞、執行指令與每輪證據保存位置。
- 跑完之後 — 保留兩輪數字,以第一輪示範交接格式。
- 背後怎麼做 — 三種分流、疑似原因與分類欄位,交代後續流程。
- 今天學到什麼 — 帶著分母回報,接到值班編排。
參考資料
- Qingzhou Luo、Farah Hariri、Lamyaa Eloussi、Darko Marinov(2014)《An Empirical Analysis of Flaky Tests》,FSE 2014 - 大規模分析 flaky 測試的成因分布,本篇五類根因的來源
- John Micco(2016)《Flaky Tests at Google and How We Mitigate Them》,Google Testing Blog - 工業規模下的 flaky 治理:偵測、隔離、解除,以及為什麼不能靠重跑
- Martin Fowler(2011)《Eradicating Non-Determinism in Tests》 - 不確定的測試會讓整套測試失去意義,以及隔離為什麼是止血不是解法
- Edwin B. Wilson(1927)《Probable Inference, the Law of Succession, and Statistical Inference》,Journal of the American Statistical Association 22(158) - 比例的信賴區間:小樣本下的重現率誤差有多大,正是「N 夠不夠大」的計算依據
- 本專案
skills/maintain/flaky-detect/SKILL.md(判準與五類根因)、tests/flaky/render-race.spec.ts(掃描實測)、tests/README.md的 flaky 那一節、references/green-cheating.md- 判準、五類根因與掃描實測的真檔