「測試執行時間每增加 10 分鐘,開發者切換上下文(Context Switch)的成本就會呈指數級上升。將 1 小時的測試壓縮到 8 分鐘內,是 SDET 給團隊最好的禮物。」
大家好,我是 Jane,一個每天在第一線處理跨平台(Web & App)自動化測試、跟 CI/CD Pipeline 執行時間與發版效率奮戰的自動化測試工程師(SDET)。
在上集 Day 22 中,我們學會了透過「變更影響分析(TIA)」與 Pytest Markers 進行精準測試挑選,避免在 PR 階段無差別跑全量測試。
但當我們面對的是 Nightly 全量迴歸(Full Regression) 或 Release 前的必跑門檻 時,專案裡累積的 300 條 API 測試與 80 條 Web/App UI 測試,如果以單執行緒(Single-thread)依序跑完,依然要耗上 1 ~ 2 個小時。
「快,還要更快!」 這是每一個資深 SDET 必須突破的效能關卡。
今天這篇文章,我們就來拆解 5 個在 Python/pytest 中縮短自動化測試執行時間的硬核優化策略!
┌─────────────────────────┐
│ 測試加速 5 大策略 │
└────────────┬────────────┘
│
┌───────────────────────────┼───────────────────────────┐
▼ ▼ ▼
【架構與平行化】 【資料與步驟下沉】 【資源與瀏覽器】
1. pytest-xdist 平行執行 3. 摒棄 UI 登入,改用 API 5. 禁用 UI 動畫與不必要資源
2. 測試分片 (CI Sharding) 4. 消除固定等待 time.sleep
將原本單線程依次執行的測試,利用 CPU 多核資源分配給多個 Worker 進程同時執行。這是效果最立竿見影(時間直接砍半甚至剩 1/4)的手段。
當單台 CI 機器(Runner)的 CPU 達到極限時,將測試套件拆分到 3~5 台獨立的 CI 機器上分散執行。
UI 測試最慢的部分往往是「前置準備」。例如:為了測試「購物車結帳」,先在 UI 上花 15 秒點擊登入、搜尋商品、加購物車。改用 API 在 0.2 秒內建好購物車 state,再直接用 UI 開啟結帳頁面!
如 Day 12 所述,全專案搜出 time.sleep() 並替換為條件式等待或自動等待。
在 Web UI 測試中阻擋圖片、字型與廣告載入,並開啟無頭模式(Headless Mode)。
pytest-xdist 平行執行在 Python 生態系中,最成熟的平行測試套件就是 pytest-xdist。
Bash
# 安裝 pytest-xdist
pip install pytest-xdist
# 方式 A: 自動偵測 CPU 核心數進行平行執行 (CPU auto)
pytest -n auto
# 方式 B: 指定建立 4 個 Worker 進程同時執行
pytest -n 4
很多團隊一開 pytest -n 4,測試就全線崩潰,這是因為原本單執行緒依序跑時,大家共用了同一個帳號或 DB 資料。
為了讓平行測試能順利執行,我們必須透過 pytest-xdist 提供的 Worker ID 進行資源隔離:
Python
# tests/conftest.py
import pytest
@pytest.fixture(scope="session")
def worker_id(request):
"""
獲取當前 pytest-xdist 的 Worker ID (例如: gw0, gw1, gw2...)
如果沒有啟用平行執行,則回傳 'master'
"""
config = getattr(request, "config", None)
if config is not None and hasattr(config, "workerinput"):
return config.workerinput["workerid"]
return "master"
@pytest.fixture(scope="function")
def isolated_user(worker_id, api_client):
"""
根據 Worker ID 建立獨立的測試使用者,徹底解決平行執行時的帳號競爭!
"""
# 每個 Worker 進程擁有專屬的前綴,絕對不會互相衝突
user_payload = {
"username": f"user_worker_{worker_id}_{uuid.uuid4().hex[:6]}",
"email": f"worker_{worker_id}@example.com",
"password": "SecurePassword123!"
}
user = api_client.create_user(user_payload)
yield user
api_client.delete_user(user["id"])
在 Web UI 測試中,載入高解析度圖片與字型檔會消耗大量的網路與 CPU 時間。我們可以在 Playwright context 中封裝資源阻擋:
Python
# tests/conftest.py
import pytest
from playwright.sync_api import Browser, BrowserContext
@pytest.fixture(scope="function")
def fast_page(browser: Browser):
"""
建立一個自動阻擋圖片與字型的極速 Page Context
"""
context: BrowserContext = browser.new_context(
viewport={"width": 1280, "height": 720}
)
page = context.new_page()
# 攔截並中斷圖片、字型與媒體檔案的下載,提升 30% 以上的載入速度!
page.route(
"**/*.{png,jpg,jpeg,svg,webp,ttf,woff,woff2}",
lambda route: route.abort()
)
yield page
context.close()
當併發進程數受限於單台 CI 機器效能時,可以使用 GitHub Actions 的 strategy.matrix 進行跨機器分片(Sharding):
YAML
# .github/workflows/nightly_sharded.yml
name: Nightly Sharded Regression
on:
schedule:
- cron: '0 18 * * *'
jobs:
run-tests-sharded:
runs-on: ubuntu-latest
strategy:
fail-fast: false
# 建立 4 台獨立的 CI 機器平行運作!
matrix:
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install pytest-split
- name: Run Sharded Tests
run: |
# 利用 pytest-split 自動將測試均勻分配到 4 台機器上執行
pytest --splits 4 --group ${{ matrix.shard }} --html=reports/shard_${{ matrix.shard }}.html
以一個包含 200 條 API 測試與 50 條 Playwright Web UI 測試的專案為例:
| 優化階段 | 採用的策略 | 總執行時間 | 效益提升 |
| 原始狀態 | 單執行緒、含有 time.sleep、全 UI 手動 Setup | 58 分鐘 | 基準點 |
| Phase 1 | 消滅 time.sleep() + 用 API 做前置 Setup | 24 分鐘 | 速度提升 58% |
| Phase 2 | 引入 pytest -n 4(4 進程平行)+ 資源阻擋 | 7 分鐘 | 速度提升 88% |
| Phase 3 | GitHub Actions 4 台機器 Sharding 分片 | 2.5 分鐘 | 極速!提升 95% |
平行測試雖然能帶來驚人的極速體驗,但許多工程師在第一次開啟 -n 4 時,卻發現原本正常的案例一開平行就全部壞掉!
常見原因包含:靜態變數互相破壞、共用帳號被踢下線、資料庫死鎖(Deadlock)以及檔案存取衝突。
明天 Day 24,我們就來專門攻克這個難題:《 Day 24|平行測試為什麼一開就全部壞掉?共享資源與 Race Condition 解決指南 》。