「Flaky Test 就像是測試體系裡的『狼來了』的故事——當 CI 上 30% 的失敗都是假警報時,團隊就會開始無視所有紅燈,自動化測試也就失去了防線的意義。」
大家好,我是 Jane,一個每天在 第一線跟 CI/CD Pipeline 上的「假紅燈」奮戰的自動化測試工程師(SDET)。
在上集 Day 12 與 Day 13 中,我們消滅了 time.sleep() 並設計了穩定的 data-testid 定位器。這些做法大幅降低了測試腳本的脆弱度。
但只要你的自動化測試數量增加、進入 CI/CD 每日執行,你就一定會遇到這個讓人崩潰的現象:
這種在相同程式碼與環境下,執行結果卻不一致(時 Pass 時 Fail)的測試,就是著名的 Flaky Test。
Flaky Test 是自動化測試最大的「信任殺手」。今天這篇文章,我們就來建立一套完整的 Flaky Test 分類與治理機制,讓 Flaky 不再成為團隊的噩夢!
當遇到 Flaky Test 時,不要盲目加 retry。首先要將問題歸類到以下 6 大根源:
┌─────────────────────────┐
│ Flaky Test 6 大根源 │
└────────────┬────────────┘
│
┌───────────────────────────┼───────────────────────────┐
▼ ▼ ▼
【測試程式碼與資料】 【產品與系統時序】 【環境與外部依賴】
1. 測試資料污染與競爭條件 3. 非同步 UI 渲染與網路延遲 5. 外部第三方 API 抖動
2. 測試案例順序依賴 (Order) 4. 產品真實的 Race Condition 6. CI Runner 資源不足 (CPU/RAM)
pytest 順序執行時通過,但換了執行順序或單獨執行 test_b.py 就失敗。test_b.py 隱性依賴了 test_a.py 留在系統中的快取、Cookie 或資料。我們來看兩個第一線排查 Flaky Test 的實務技巧:
pytest-random-order)如果懷疑測試案例之間存在狀態污染或順序依賴,可以使用 pytest-random-order 套件,隨機打亂測試執行順序:
Bash
# 安裝隨機順序套件
pip install pytest-random-order
# 以隨機順序執行所有測試,並記錄 Seed 號碼
pytest --random-order
# 如果某次失敗了,使用相同的 Seed 號碼重現失敗的執行順序
pytest --random-order-seed=123456
確保每個案例執行前後,環境與狀態都是乾淨的:
Python
# tests/conftest.py
import pytest
from playwright.sync_api import Page
@pytest.fixture(autouse=True)
def cleanup_browser_context(page: Page):
"""每個案例執行後,自動清除 Cookies 與 LocalStorage,避免狀態污染"""
yield
page.context.clear_cookies()
page.evaluate("window.localStorage.clear();")
page.evaluate("window.sessionStorage.clear();")
避免在測試案例中使用固定字串(如 user_test_01),改用 UUID 或動態時間戳記:
Python
# tests/test_user_registration.py
import uuid
from playwright.sync_api import Page, expect
def test_user_registration_should_succeed(page: Page):
page.goto("https://admin.example.com/register")
# 動態產生唯一的測試帳號與 Email,徹底解決平行執行時的資料衝突
unique_id = str(uuid.uuid4())[:8]
unique_email = f"test_user_{unique_id}@example.com"
page.get_by_test_id("email-input").fill(unique_email)
page.get_by_test_id("password-input").fill("SecurePass123!")
page.get_by_test_id("register-btn").click()
expect(page.get_by_test_id("welcome-banner")).to_be_visible()
當專案中有上百條案例時,我們不可能當天修完所有的 Flaky Test。如何不讓少數幾條 Flaky Test 拖垮整個團隊對 CI 的信任?
我們應該建立 「測試隔離區機制(Quarantine Mechanism)」:
┌─────────────────────────┐
│ Flaky Test 治理三部曲 │
└────────────┬────────────┘
│
┌───────────────────────────┼───────────────────────────┐
▼ ▼ ▼
1. 偵測與標記 (Quarantine) 2. 移出主 Line (Quarantine) 3. 定期修復/刪除 (Fix/Delete)
標記 @pytest.mark.flaky 不影響 PR Merge 門檻 每週 Sprint 清理技術債
一旦發現某條測試出現無規律的 Flaky,立刻將其打上記號移入隔離區,不要讓它繼續擋住開發團隊 Merge PR。
在 pytest 中,可以使用自訂 Marker 或 flaky 標籤:
Python
# pytest.ini 註冊 marker
[pytest]
markers =
flaky: 標記為不穩定的測試,隔離進行觀察與修復
Python
# tests/test_order.py
import pytest
@pytest.mark.flaky
def test_complex_order_flow(page):
"""這條測試已知有時序 Flaky,暫時隔離排查中"""
...
在 CI 配置中,將標記為 flaky 的測試從 blocking Pipeline 中排除:
Bash
# 1. 核心 CI 命令(Blocking):只跑穩定的測試,失敗就阻擋 PR Merge
pytest -m "not flaky"
# 2. 隔離區 CI 命令(Non-blocking):定時執行,收集 Flaky 數據供 QA 排查
pytest -m "flaky" --continue-on-collection-errors
被隔離的 Flaky Test 必須有生命週期上限(例如:最多給予 2 個 Sprint 的修復時間)。
如果排查後發現維護成本極高、且商業價值低,請果斷刪除它! 保留一條隨機跳紅燈的脆弱測試,對團隊品質健康的傷害遠高於沒有這條測試。
pytest-rerunfailures 重跑 3 次。重重重試只是把問題「掩蓋」起來(Day 15 會專文討論 Retry 的副作用)。說到了 Flaky Test,許多團隊第一時間採取的應對措施就是裝上 pytest-rerunfailures 插件,讓失敗的測試自動重試 3 次。
但「Retry(自動重試)」真的能解決問題嗎?還是它只是在飲鴆止渴?
明天 Day 15,我們將深入剖析:《 Day 15|Retry 不是解決 Flaky test 的方法:重試機制的陷阱與隔離策略 》。