iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0
Software Development

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

Day 23|如何縮短自動化測試執行時間:平行執行與極速優化實戰

  • 分享至 

  • xImage
  •  

「測試執行時間每增加 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 大優化金字塔

                    ┌─────────────────────────┐
                    │    測試加速 5 大策略      │
                    └────────────┬────────────┘
                                 │
     ┌───────────────────────────┼───────────────────────────┐
     ▼                           ▼                           ▼
【架構與平行化】             【資料與步驟下沉】            【資源與瀏覽器】
1. pytest-xdist 平行執行     3. 摒棄 UI 登入,改用 API    5. 禁用 UI 動畫與不必要資源
2. 測試分片 (CI Sharding)    4. 消除固定等待 time.sleep

1. 多進程平行執行(Parallel Execution)

將原本單線程依次執行的測試,利用 CPU 多核資源分配給多個 Worker 進程同時執行。這是效果最立竿見影(時間直接砍半甚至剩 1/4)的手段。

2. CI Pipeline 分片(CI Test Sharding)

當單台 CI 機器(Runner)的 CPU 達到極限時,將測試套件拆分到 3~5 台獨立的 CI 機器上分散執行。

3. 以 API 替代 UI 進行 Setup / Teardown(Arrange 下沉)

UI 測試最慢的部分往往是「前置準備」。例如:為了測試「購物車結帳」,先在 UI 上花 15 秒點擊登入、搜尋商品、加購物車。改用 API 在 0.2 秒內建好購物車 state,再直接用 UI 開啟結帳頁面!

4. 徹底消除固定等待(Eliminate Magic Sleep)

如 Day 12 所述,全專案搜出 time.sleep() 並替換為條件式等待或自動等待。

5. 輕量化瀏覽器與 context(Resource Lightweighting)

在 Web UI 測試中阻擋圖片、字型與廣告載入,並開啟無頭模式(Headless Mode)。

二、 Python / pytest 實戰:pytest-xdist 平行執行

在 Python 生態系中,最成熟的平行測試套件就是 pytest-xdist

1. 安裝與基礎平行執行指令

Bash

# 安裝 pytest-xdist
pip install pytest-xdist

# 方式 A: 自動偵測 CPU 核心數進行平行執行 (CPU auto)
pytest -n auto

# 方式 B: 指定建立 4 個 Worker 進程同時執行
pytest -n 4

2. 平行執行的核心挑戰:資料競爭與併發隔離

很多團隊一開 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"])

3. Playwright 瀏覽器輕量化加速(阻擋不必要的媒體資源)

在 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()

三、 GitHub Actions CI 多機器分片(Sharding)範例

當併發進程數受限於單台 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

四、 第一線 SDET 的加速效能對比(Before vs. After)

以一個包含 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 解決指南 》


上一篇
Day 22|不是所有測試都該每次全部執行:測試標籤分類與受影響範圍抽離
系列文
從脆弱腳本到可信任測試平台:自動化測試架構30天23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言