iT邦幫忙

2026 iThome 鐵人賽

DAY 14
1
Software Development

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

Day 14|Flaky test到底該怎麼處理?建構排查分類與治理機制

  • 分享至 

  • xImage
  •  

「Flaky Test 就像是測試體系裡的『狼來了』的故事——當 CI 上 30% 的失敗都是假警報時,團隊就會開始無視所有紅燈,自動化測試也就失去了防線的意義。」

大家好,我是 Jane,一個每天在 第一線跟 CI/CD Pipeline 上的「假紅燈」奮戰的自動化測試工程師(SDET)。

在上集 Day 12 與 Day 13 中,我們消滅了 time.sleep() 並設計了穩定的 data-testid 定位器。這些做法大幅降低了測試腳本的脆弱度。

但只要你的自動化測試數量增加、進入 CI/CD 每日執行,你就一定會遇到這個讓人崩潰的現象:

  • 「這條測試在我的 Local 電腦跑 10 次全過,放到 CI 上跑 10 次卻隨機失敗 2 次;重跑(Re-run)一次又過了!」

這種在相同程式碼與環境下,執行結果卻不一致(時 Pass 時 Fail)的測試,就是著名的 Flaky Test

Flaky Test 是自動化測試最大的「信任殺手」。今天這篇文章,我們就來建立一套完整的 Flaky Test 分類與治理機制,讓 Flaky 不再成為團隊的噩夢!

一、 追根究底:Flaky Test 的 6 大根源分類

當遇到 Flaky Test 時,不要盲目加 retry。首先要將問題歸類到以下 6 大根源:

                    ┌─────────────────────────┐
                    │   Flaky Test 6 大根源   │
                    └────────────┬────────────┘
                                 │
     ┌───────────────────────────┼───────────────────────────┐
     ▼                           ▼                           ▼
【測試程式碼與資料】           【產品與系統時序】           【環境與外部依賴】
1. 測試資料污染與競爭條件      3. 非同步 UI 渲染與網路延遲  5. 外部第三方 API 抖動
2. 測試案例順序依賴 (Order)   4. 產品真實的 Race Condition 6. CI Runner 資源不足 (CPU/RAM)

1. 測試資料污染與競爭條件(Data Contamination & Race Condition)

  • 現象:單獨跑案例會過,但跟其他案例一起跑或平行執行(Parallel)時就會失敗。
  • 原因:多個案例共用了同一個測試帳號或修改了同一筆 DB 資料。

2. 測試案例順序依賴(Test Order Dependency)

  • 現象pytest 順序執行時通過,但換了執行順序或單獨執行 test_b.py 就失敗。
  • 原因test_b.py 隱性依賴了 test_a.py 留在系統中的快取、Cookie 或資料。

3. 非同步 UI 渲染與時序問題(Asynchronous Timing)

  • 現象:CI 伺服器效能較差時容易失敗,Local 電腦效能好時很順暢。
  • 原因:沒有正確等待 API 回傳或 DOM 渲染完畢(Day 12 已提供條件等待解法)。

4. 產品真實的 Race Condition(真正隱藏的 Bug!)

  • 現象:偶發性失敗,排查後發現是前端與後端在極端時序下的非同步 Bug。
  • 價值這不是測試程式碼壞掉,這是自動化測試幫產品抓到的高價值 Bug!

5. 外部第三方服務依賴(External Dependency)

  • 現象:深夜跑測試很穩定,白天跑測試容易失敗。
  • 原因:測試依賴了外部金流 Sandbox、簡訊驗證碼 API 或第三方 Captcha,外部服務抖動導致測試失敗。

6. CI 基礎設施資源不足(Infrastructure Flakiness)

  • 現象:當 CI 同時跑多個 Build Job 時,測試失敗率突然飆升。
  • 原因:Docker 容器 CPU 飆到 100%、記憶體不足(OOM)導致瀏覽器無響應。

二、 Python / pytest 實戰:排查與修復 Flaky Test

我們來看兩個第一線排查 Flaky Test 的實務技巧:

技巧 1:排查測試案例順序依賴(使用 pytest-random-order

如果懷疑測試案例之間存在狀態污染或順序依賴,可以使用 pytest-random-order 套件,隨機打亂測試執行順序:

Bash

# 安裝隨機順序套件
pip install pytest-random-order

# 以隨機順序執行所有測試,並記錄 Seed 號碼
pytest --random-order

# 如果某次失敗了,使用相同的 Seed 號碼重現失敗的執行順序
pytest --random-order-seed=123456

修復策略:利用 pytest Fixture 做好 Autouse Teardown

確保每個案例執行前後,環境與狀態都是乾淨的:

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();")

技巧 2:排查資料競爭與動態唯一資料 generate

避免在測試案例中使用固定字串(如 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()

三、 第一線 QA 的 Flaky Test 治理機制(Quarantine Protocol)

當專案中有上百條案例時,我們不可能當天修完所有的 Flaky Test。如何不讓少數幾條 Flaky Test 拖垮整個團隊對 CI 的信任?

我們應該建立 「測試隔離區機制(Quarantine Mechanism)」

                    ┌─────────────────────────┐
                    │  Flaky Test 治理三部曲  │
                    └────────────┬────────────┘
                                 │
     ┌───────────────────────────┼───────────────────────────┐
     ▼                           ▼                           ▼
1. 偵測與標記 (Quarantine)      2. 移出主 Line (Quarantine)     3. 定期修復/刪除 (Fix/Delete)
   標記 @pytest.mark.flaky      不影響 PR Merge 門檻          每週 Sprint 清理技術債

1. 標記並隔離(Quarantine)

一旦發現某條測試出現無規律的 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,暫時隔離排查中"""
    ...

2. CI Pipeline 分流執行

在 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

3. 治理原則:修不好就刪除(Fix or Delete)

被隔離的 Flaky Test 必須有生命週期上限(例如:最多給予 2 個 Sprint 的修復時間)。
如果排查後發現維護成本極高、且商業價值低,請果斷刪除它! 保留一條隨機跳紅燈的脆弱測試,對團隊品質健康的傷害遠高於沒有這條測試。

四、 第一線 QA 的心法總結

  1. Retry(自動重試)不是修復 Flaky 的方法
    許多人會設定 pytest-rerunfailures 重跑 3 次。重重重試只是把問題「掩蓋」起來(Day 15 會專文討論 Retry 的副作用)。
  2. 對 Flaky 保持零容忍(Zero Tolerance)
    不要習慣性地對同事說:「喔,CI 上那個紅燈是 Flaky,不用理它,重跑一次就會過。」這句說出口時,自動化測試的價值就已經折損了一半。
  3. 把 Flaky 當成產品品質的早期訊號
    有時候測試的 Flaky,正是因為前端組件的生命週期(Lifecycle)處理不當或後端 API 存在 Race Condition。認真排查每一次 Flaky,你可能會幫產品挽救一次線上事故。

明日預告

說到了 Flaky Test,許多團隊第一時間採取的應對措施就是裝上 pytest-rerunfailures 插件,讓失敗的測試自動重試 3 次。

但「Retry(自動重試)」真的能解決問題嗎?還是它只是在飲鴆止渴?

明天 Day 15,我們將深入剖析:《 Day 15|Retry 不是解決 Flaky test 的方法:重試機制的陷阱與隔離策略 》


上一篇
Day 13|如何設計比較穩定的定位器:從 XPath 陷阱到 data-testid 實戰
系列文
從脆弱腳本到可信任測試平台:自動化測試架構30天14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言