iT邦幫忙

2026 iThome 鐵人賽

DAY 15
1
Software Development

從脆弱腳本到可信任測試平台:自動化測試架構30天系列 第 15

Day 15|Retry不是解決Flaky test的方法:重試機制的陷阱與隔離策略

  • 分享至 

  • xImage
  •  

「對失敗的測試加 retry: 3,就像是用黑膠帶把發出異音的引擎轉速表貼起來——問題沒有消失,你只是選擇眼不見為淨。」

大家好,我是 Jane,一個每天在第一線處理跨平台(Web & App)自動化測試、跟 CI/CD Pipeline 與 Flaky Test 奮戰的自動化測試工程師(SDET)。

在上集 Day 14 中,我們拆解了 Flaky Test 的 6 大根源,並建立了隔離區(Quarantine)機制。

當許多 QA 面對測試不穩定時,第一時間最常採取的「快捷方式」就是直接在測試框架(如 pytestplaywright)設定:

Ini, TOML

# pytest.ini
addopts = --reruns 3 --reruns-delay 2

看似簡單一行設定,CI 上的紅燈立刻少了一半,團隊一片歡騰。

但身為負責品質防線的 SDET,我們必須誠實面對:Retry(自動重試)絕不是解決 Flaky Test 的真答案,盲目使用重試機制,往往是在飲鴆止渴。

今天這篇文章,我們就來好好剖析:重試機制的副作用、哪些情境才適合重試,以及如何設計一套「不掩蓋問題」的重試與隔離策略!

一、 Retry 機制的三大隱形殺手(Side Effects)

                    ┌─────────────────────────┐
                    │   Retry 機制的三大副作用 │
                    └────────────┬────────────┘
                                 │
     ┌───────────────────────────┼───────────────────────────┐
     ▼                           ▼                           ▼
1. 掩蓋產品真實 Race Condition  2. CI 執行時間成倍飆升      3. 測試資料與狀態二度污染

1. 掩蓋產品真實的 Race Condition(品質盲點)

許多 Flaky Test 不是測試程式碼寫爛,而是產品前端與後端 API 在極端網路時序下的真實問題(例如:按鈕重複點擊、非同步狀態同步失敗)。如果設了 Retry,第一次失敗但第二次重試成功了,這個能抓到真實 Bug 的訊號就被徹底掩蓋掉了。

2. CI 執行時間倍數成長(Execution Bottleneck)

假設一個 UI 案例執行需 30 秒,如果框架中有 10 個不穩定的案例,每個案例都重試 3 次,單單這 10 個案例就會多消耗 15~20 分鐘 的 CI 資源!團隊等 PR Merge 的時間被無限拉長。

3. 測試資料與狀態污染(Data Corruption)

如果案例在第 3 步「建立訂單」成功後,在第 4 步驗證失敗觸發 Retry;當它重試第 1 步時,資料庫裡已經存在剛才建好的訂單資料,這會直接引發第二次重試因「資料重複」而再次失敗。

二、 什麼狀況「可以 Retry」?什麼狀況「絕不 Retry」?

並非不能用 Retry,而是要精準區分適用情境:

類別 情境說明 是否允許 Retry? 處置建議
環境與網路瞬斷 測試環境 DNS 抖動、第三方金流 API 瞬斷 (503 Service Unavailable) ✅ 允許 (有限度) 僅限特定 Network Error 觸發,且需記錄首次失敗 Log。
產品商業邏輯 轉帳、扣款、建立使用者、狀態流轉斷言 ❌ 絕不 Retry 一旦失敗即確定為 Issue 或測試邏輯錯誤,需立刻排查。
UI 元素狀態 定位器找不到、DOM 狀態未更新 ❌ 絕不 Retry 應透過顯式等待(expect / wait_for)解決(Day 12),而非 Retry。

三、 Python / pytest 實戰:優雅記錄與隔離首次失敗

如果一定要使用重試機制,絕對不能讓第一次失敗「無痕消失」。我們必須在 pytest 中紀錄首次失敗的資訊,並結合隔離標籤。

1. 利用 pytest-rerunfailures 進行精準重試設定

不要在全域開啟無差別重試,改為針對特定外部依賴案例進行標記:

Python

# tests/test_payment_gateway.py
import pytest
from playwright.sync_api import Page, expect

# 僅針對高度依賴外部第三方 Sandbox 的案例允許重試 2 次
@pytest.mark.flaky(reruns=2, reruns_delay=1)
def test_third_party_payment_sandbox(page: Page):
    """
    第三方金流服務 Sandbox 極易抖動,此處允許有限度重試
    """
    page.goto("https://staging.example.com/checkout")
    # 操作第三方金流 ...
    expect(page.get_by_test_id("payment-success")).to_be_visible()

2. 在 conftest.py 中捕捉重試紀錄並保存首次失敗資訊

確保每一次 Retry 的第一次失敗截圖與 Log 都有被保留,以供日後排查:

Python

# tests/conftest.py
import pytest
from playwright.sync_api import Page

@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    report = outcome.get_result()

    # 檢查該案例是否觸發了 rerun (重試)
    if hasattr(report, "rerun") and report.rerun > 0:
        print(f"\n[Warning] 案例 {item.name} 正在進行第 {report.rerun} 次重試!")

    # 如果重試過程中發生失敗,立刻抓取當下螢幕截圖與 Trace
    if report.when == "call" and report.failed:
        page: Page = item.funcargs.get("page")
        if page:
            screenshot_path = f"reports/screenshots/retry_{report.rerun}_{item.name}.png"
            page.screenshot(path=screenshot_path)
            print(f"[Screenshot] 失敗截圖已儲存至: {screenshot_path}")

四、 SDET 的 Quarantine(隔離區)健全策略

比起無腦 Retry,資深 SDET 更傾向採用 Quarantine(隔離機制)

  1. 首次失敗記錄:案例失敗時,先透過 CI Pipeline 進行 1 次環境檢查重試。
  2. 自動進入待修區:如果第二次重試過關,代表該案例存在時序或 Flaky 隱患,系統自動發送 Slack 通知並打上 quarantine 標籤。
  3. 退回開發/測試 Backlog:被隔離的案例會被移出 PR 合併門檻(Non-blocking),由 SDET 在專門的 Sprint 整理時間進行重構或修復。

明日預告

恭喜大家完成了「第三部分:處理最容易讓測試變脆弱的問題(Day 11~15)」!

我們解決了等待時間、定位器、Flaky test 與 Retry 陷阱。從明天開始,我們將跨入全新的:

《第四部分:測試資料、環境與 API 整合》

明天 Day 16,我們將來探討自動化測試中最硬核的底層問題:

《 Day 16|測試資料為什麼比測試程式更難管理?剖析資料依賴與污染的陷阱 》


上一篇
Day 14|Flaky test到底該怎麼處理?建構排查分類與治理機制
下一篇
Day 16|測試資料為什麼比測試程式更難管理?剖析資料依賴與污染的陷阱
系列文
從脆弱腳本到可信任測試平台:自動化測試架構30天23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言