iT邦幫忙

2026 iThome 鐵人賽

DAY 22
1
Software Development

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

Day 22|不是所有測試都該每次全部執行:測試標籤分類與受影響範圍抽離

  • 分享至 

  • xImage
  •  

「每次改動兩行 CSS 或修復一個後端小 Bug,就觸發長達 2 小時的全套 800 條自動化測試,這不叫嚴謹,這叫資源浪費與開發阻力。」

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

在上集 Day 21 中,我們建立了 CI/CD Pipeline 的 5 大測試階段(PR 門檻、Post-Merge、Nightly、Smoke、Production Check)。

當專案規模持續成長,團隊會遇到另一個極度真實的瓶頸:

  • 「雖然我們把 UI 測試移到了 Nightly 階段,但光是 API 測試案例就膨脹到了 600 條,每次 PR CI光跑 API 就要 20 分鐘,這該怎麼辦?」

許多團隊對自動化的迷思是「寧可錯殺千條,不可放過一條」,要求每次 CI 必須無差別地執行全部測試。但對第一線 SDET 來說,「精準測試(Smart Testing)」才是兼顧速度與品質防線的硬實力。

今天這篇文章,我們就來好好聊聊:如何透過 Pytest 標籤架構與 Git Diff 受影響範圍分析(Test Impact Analysis),做到『只跑需要的測試』!

一、 測試分類矩陣:建立三維度標籤 (Pytest Markers)

要做到精準執行,第一步是在 pytest 專案中建立結構化的 Marker 標籤體系。建議從以下 3 個維度組合標籤:

                    ┌─────────────────────────┐
                    │    Pytest 三維度標籤     │
                    └────────────┬────────────┘
                                 │
     ┌───────────────────────────┼───────────────────────────┐
     ▼                           ▼                           ▼
1. 關鍵程度 (Criticality)   2. 模組/功能區塊 (Module)     3. 執行成本/類型 (Type/Cost)
   - @pytest.mark.p0          - @pytest.mark.checkout       - @pytest.mark.api
   - @pytest.mark.p1          - @pytest.mark.auth           - @pytest.mark.ui
   - @pytest.mark.p2          - @pytest.mark.user           - @pytest.mark.slow

1. 關鍵程度維度 (P0 / P1 / P2)

  • P0 (Critical Path):核心主流程(如:登入、結帳付款、註冊)。系統壞了會直接損失營收。
  • P1 (Core Feature):主要功能細節(如:優惠券折抵、個人資料修改、訂單列表篩選)。
  • P2 (Edge Case):罕見邊界條件與格式異常驗證。

2. 模組與領域維度 (Module / Domain)

  • 標記該測試屬於系統的哪一個業務區塊(如:auth, payment, cart, notification)。

3. 執行類型與速度維度 (Type / Speed)

  • 區分 api, ui, mobile 以及是否為 slow(執行時間超過 10 秒的案例)。

二、 精準執行策略:從全量執行到受影響範圍分析 (TIA)

┌─────────────────────────────────────────────────────────────────┐
│                      3 種 CI 測試執行模式                        │
├─────────────────────────────────────────────────────────────────┤
│ 1. 全量執行 (Full Run)        ──▶ 適用於 Nightly / Release      │
│ 2. 標籤篩選 (Tag-based Run)   ──▶ 適用於 PR / Smoke (如: P0 API)│
│ 3. 受影響範圍執行 (Diff-based) ──▶ 適用於 Feature 分支開頭       │
└───────────────────────────────────────────────────────────── ───┘

策略:依據 Git 改動檔案動態挑選測試(Git-Diff Impact Analysis)

如果開發工程師這次的 PR 只修改了 payment/ 模組的後端程式碼,為什麼 CI 要去執行 auth/user/ 的測試?

我們可以在 CI/CD 中透過 Python 腳本分析 git diff 改動的檔案路徑,自動對應並動態組裝 pytest 執行指令!

三、 Python / pytest 實戰:受影響範圍自動化挑選器

我們寫一個輕量級的 Python 工具 scripts/smart_test_runner.py,來實現這套「變更影響分析」:

1. 撰寫受影響範圍分析指令 (scripts/smart_test_runner.py)

Python

# scripts/smart_test_runner.py
import subprocess
import sys

# 定義「產品程式碼路徑」與「Pytest 模組標籤」的映射關係
MODULE_MAP = {
    "services/auth/": "auth",
    "services/payment/": "payment",
    "services/cart/": "cart",
    "services/user/": "user",
}

def get_git_changed_files() -> list[str]:
    """獲取與 main 分支對比時,本次 PR 改動的所有檔案清單"""
    try:
        # 執行 git diff 取得改動檔案
        result = subprocess.run(
            ["git", "diff", "--name-only", "origin/main...HEAD"],
            capture_output=True,
            text=True,
            check=True
        )
        return [line.strip() for line in result.stdout.splitlines() if line.strip()]
    except Exception as e:
        print(f"[Warning] 無法獲取 git diff,將回退至執行預設門檻: {e}")
        return []

def resolve_pytest_markers(changed_files: list[str]) -> str:
    """根據改動檔案,組裝 pytest -m 參數"""
    matched_markers = set()

    for file_path in changed_files:
        for code_path, marker in MODULE_MAP.items():
            if file_path.startswith(code_path):
                matched_markers.add(marker)

    # 如果改到公共套件 (如 config/ 或 utils/) 或抓不到對應,則回退執行全套 P0 測試
    if not matched_markers or any(f.startswith("utils/") or f.startswith("config/") for f in changed_files):
        print("[Info] 偵測到基礎架構改動,將執行全套 P0 門檻測試...")
        return "p0"

    # 如果只改動了特定模組,則回傳該模組的 P0/P1 測試
    # 例如:(payment or cart) and (p0 or p1)
    modules_query = " or ".join(matched_markers)
    return f"({modules_query}) and (p0 or p1)"

if __name__ == "__main__":
    changed_files = get_git_changed_files()
    print(f"[Smart Runner] 偵測到的改動檔案數: {len(changed_files)}")

    marker_expression = resolve_pytest_markers(changed_files)
    pytest_cmd = f"pytest -m '{marker_expression}' --junitxml=reports/smart_report.xml"

    print(f"[Smart Runner] 即將執行精準測試指令: {pytest_cmd}")
    # 執行 Pytest
    exit_code = subprocess.call(pytest_cmd, shell=True)
    sys.exit(exit_code)

2. 在 GitHub Actions CI 中呼叫 Smart Runner

YAML

# .github/workflows/pr_ci.yml
name: PR Smart Test Gate

on:
  pull_request:
    branches: [ main ]

jobs:
  smart-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # 確保抓取完整 git 歷史,以便計算 git diff

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: |
          pip install -r requirements.txt
      - name: Run Smart Test Selection
        run: |
          # 執行我們的精準測試分析腳本!
          python scripts/smart_test_runner.py

四、 第一線 SDET 的精準測試心法

  1. 精準測試不能取代 Nightly 全量測試
    「受影響範圍分析」是為了給開發者在 PR 階段獲得 3 分鐘內的極速回饋。到了深夜(Nightly Build)或 Release 前夕,依然必須執行 100% 的全量 Regression 測試,防止隱蔽的跨模組邊界問題。
  2. 標籤維護是團隊的共同契約
    新增測試案例時,必須嚴格打上標籤(如 @pytest.mark.p0@pytest.mark.checkout)。可以在 Code Review 流程中將「是否有正確標記 Marker」列入 Review 清單。
  3. 監控「漏網之魚」(Escaped Bugs)
    如果發現某次線上事故是因為精準測試「太過精準而漏掉了某個未標記的測試」,立刻回頭檢視 smart_test_runner.py 的模組對映邏輯與 Marker 規則。

明日預告

透過「受影響範圍分析」與「標籤分層」,我們大幅減少了 PR 階段不必要的測試執行。

但當全量 Nightly 測試包含了上百條昂貴的 Web / App UI 案例時,就算只跑必要的案例,單線程(Single-thread)執行依然要花上幾小時。

「我們該如何透過平行測試(Parallel Testing)與併發執行,將 1 小時的執行時間壓縮到 10 分鐘內?」

明天 Day 23,我們將深入探討:《 Day 23|如何縮短自動化測試執行時間:平行執行與極速優化實戰 》


上一篇
Day 21|測試在Pipeline裡應該放在哪裡?建構高效的 CI/CD 測試階段
下一篇
Day 23|如何縮短自動化測試執行時間:平行執行與極速優化實戰
系列文
從脆弱腳本到可信任測試平台:自動化測試架構30天23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言