「面對一套爛掉的測試框架,最衝動的作法是直接宣告『我要全部砍掉重寫』;而最成熟的作法,是像進行心臟外科手術一樣,在系統持續執行的同時,完成器官置換與血管重構。」
大家好,我是 Jane,一個每天在第一線處理跨平台(Web & App)自動化測試、跟 CI/CD Pipeline 與各種歷史遺留架構(Legacy Framework)奮戰的自動化測試工程師(SDET)。
在上集 Day 27 中,我們剖析了自動化測試技術債產生的 7 大坑洞,並提出了漸進式清債的理念。
但在現實職場中,我們最常面臨的硬核挑戰往往是:
time.sleep,CI 上天天紅綠燈交替。團隊的人都對你說:『那個測試很敏感,沒事別去動它!』」
當一套測試框架變成「誰動誰倒楣」的黑盒子時,我們該如何系統化地重構它,讓它重新獲得團隊的信任?
今天這篇文章,我們就來手把手制定一套 第一線 SDET 的舊測試框架重構 5 階段藍圖(Refactoring Blueprint)!
為什麼不能直接砍掉重寫?因為舊框架雖然爛,但它裡面依然包含著過去兩年團隊累積的商業邊界邏輯(Known Business Rules)。直接砍掉重寫,往往意味著你會把過去已經被攔截過的 Bug 重新放漏出去。
請遵循以下 5 個階段進行「外科手術式」重構:
┌─────────────────────────┐
│ 舊框架重構 5 階段藍圖 │
└────────────┬────────────┘
│
┌───────────────────────────┼───────────────────────────┐
▼ ▼ ▼
1. 建立現況數據防線 3. 分離測試資料與環境變數 5. 模組化 Page/Component
(Baseline Metrics) (Decouple Data & Config) (Modularize Objects)
2. 修復執行與報告能力 4. 抽離通用生命週期 Fixtures
(Fix Runner & Reporter) (Standardize Fixtures)
pytest-html 或 Allure Report,並加入 Day 25 的失敗自動截圖與 Trace 鉤子。@pytest.mark.quarantine 標籤移出 CI 門檻。https://...。config/settings.py 與 .env 機制,將環境設定抽離;改用動態 UUID 或 API 產生測試帳號。tests/conftest.py。page.click("div > ul > li:nth-child(2)"))。我們來看一段真實的舊代碼,以及如何透過上述藍圖逐步將其手術重構:
Python
# legacy_tests/test_old_cart.py
# 痛點: 沒有使用 pytest fixture、網址與帳密寫死、含有 time.sleep、沒有 Page Object
import time
from playwright.sync_api import sync_playwright
def test_add_to_cart_legacy():
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
# 1. 硬編碼登入
page.goto("https://staging-shop.example.com/login")
time.sleep(3)
page.fill("#username", "admin_test_01")
page.fill("#password", "123456")
page.click("#login-btn")
time.sleep(3)
# 2. 硬編碼操作與脆弱定位器
page.goto("https://staging-shop.example.com/product/99")
time.sleep(2)
page.click("div.prod-detail > button.btn-add")
time.sleep(2)
# 3. 脆弱的文字斷言
assert "已加入購物車" in page.content()
browser.close()
第一步:抽離全域 conftest.py(階段 3 & 4)
Python
# tests/conftest.py
import pytest
from config.settings import settings
from utils.api_client import APIClient
from pages.product_page import ProductPage
@pytest.fixture(scope="function")
def authenticated_page(page, api_client, random_user):
"""
將登入邏輯全數下沉至 Fixture,並透過 API 快速完成 Session 注入,徹底消滅 UI 登入等待!
"""
# 透過 API 快速為動態帳號進行認證
token = api_client.get_token(random_user["username"], random_user["password"])
# 注入 Cookie / StorageState 至 Playwright Context
page.context.add_cookies([{
"name": "session_token",
"value": token,
"domain": settings.DOMAIN,
"path": "/"
}])
return page
第二步:重構後的測試案例層 test_cart.py(階段 5)
Python
# tests/shopping/test_cart.py
from playwright.sync_api import expect
from pages.product_page import ProductPage # 模組化的 Page Object
def test_add_product_to_cart_succeeds(authenticated_page, user_factory):
"""
重構後的測試案例:
1. 透過 authenticated_page 避開昂貴的 UI 登入
2. 使用 ProductPage 封裝 DOM 定位與操作
3. 採用 Web-first Assertion 自動等待狀態
"""
product_page = ProductPage(authenticated_page)
# Act: 進入商品頁並加入購物車 (使用組態控制的 URL)
product_page.navigate_to_product(product_id="PROD_99")
product_page.add_to_cart()
# Assert: 斷言 Toast 訊息顯現
expect(product_page.toast_notification).to_have_text("已加入購物車")
重構舊框架不只是技術問題,更是團隊信任問題。在重構期間,請記住以下 3 個溝通心法:
每週向團隊展示「數據改善(Metrics Improvement)」
製作簡單的對比圖表向 PM 與主管匯報:
「本週完成了 Checkout 模組的重構:執行時間從 18 分鐘 降至 3 分鐘,Flaky 失敗率從 25% 降至 0%。」
用數據證明你正在讓團隊的開發變快,而不是在做無用功。
遵守「雙軌並行(Strangler Fig Pattern)」原則
在重構初期,讓新的 pytest 專案結構與舊的 legacy 腳本在 CI 上並行運作。每當重構好一個模組,就把舊專案裡對應的案例關閉(Delete/Disable)。當舊專案的案例歸零時,重構即完美宣告結束!
讓開發工程師(Dev)參與 Review 新的架構
主動邀請 Dev 來 Review 你的 conftest.py 與 settings.py。當 Dev 發現新的測試程式碼乾淨、易懂且可在 Local 快速跑過時,他們就會願意開始主動幫忙寫測試!
告別了舊框架重構的硬核實務後,我們將邁入 2026 年軟體工程與自動化測試領域最熱門、也最不可忽視的時代議題:
「AI(LLM)到底可以怎麼幫助自動化測試?資深 SDET 該如何善用 AI 工具,同時避開 AI 產出虛假斷言的陷阱?」
明天 Day 29,我們將深入探討:《 Day 29|AI 可以怎麼幫助自動化測試:2026 年 SDET 的 AI 實戰指南 》。