「每次改動兩行 CSS 或修復一個後端小 Bug,就觸發長達 2 小時的全套 800 條自動化測試,這不叫嚴謹,這叫資源浪費與開發阻力。」
大家好,我是 Jane,一個每天在第一線處理跨平台(Web & App)自動化測試、跟 CI/CD Pipeline 效能與發版節奏奮戰的自動化測試工程師(SDET)。
在上集 Day 21 中,我們建立了 CI/CD Pipeline 的 5 大測試階段(PR 門檻、Post-Merge、Nightly、Smoke、Production Check)。
當專案規模持續成長,團隊會遇到另一個極度真實的瓶頸:
許多團隊對自動化的迷思是「寧可錯殺千條,不可放過一條」,要求每次 CI 必須無差別地執行全部測試。但對第一線 SDET 來說,「精準測試(Smart Testing)」才是兼顧速度與品質防線的硬實力。
今天這篇文章,我們就來好好聊聊:如何透過 Pytest 標籤架構與 Git Diff 受影響範圍分析(Test Impact Analysis),做到『只跑需要的測試』!
要做到精準執行,第一步是在 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
auth, payment, cart, notification)。api, ui, mobile 以及是否為 slow(執行時間超過 10 秒的案例)。┌─────────────────────────────────────────────────────────────────┐
│ 3 種 CI 測試執行模式 │
├─────────────────────────────────────────────────────────────────┤
│ 1. 全量執行 (Full Run) ──▶ 適用於 Nightly / Release │
│ 2. 標籤篩選 (Tag-based Run) ──▶ 適用於 PR / Smoke (如: P0 API)│
│ 3. 受影響範圍執行 (Diff-based) ──▶ 適用於 Feature 分支開頭 │
└───────────────────────────────────────────────────────────── ───┘
如果開發工程師這次的 PR 只修改了 payment/ 模組的後端程式碼,為什麼 CI 要去執行 auth/ 或 user/ 的測試?
我們可以在 CI/CD 中透過 Python 腳本分析 git diff 改動的檔案路徑,自動對應並動態組裝 pytest 執行指令!
我們寫一個輕量級的 Python 工具 scripts/smart_test_runner.py,來實現這套「變更影響分析」:
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)
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
@pytest.mark.p0 與 @pytest.mark.checkout)。可以在 Code Review 流程中將「是否有正確標記 Marker」列入 Review 清單。smart_test_runner.py 的模組對映邏輯與 Marker 規則。透過「受影響範圍分析」與「標籤分層」,我們大幅減少了 PR 階段不必要的測試執行。
但當全量 Nightly 測試包含了上百條昂貴的 Web / App UI 案例時,就算只跑必要的案例,單線程(Single-thread)執行依然要花上幾小時。
「我們該如何透過平行測試(Parallel Testing)與併發執行,將 1 小時的執行時間壓縮到 10 分鐘內?」
明天 Day 23,我們將深入探討:《 Day 23|如何縮短自動化測試執行時間:平行執行與極速優化實戰 》。