iT邦幫忙

2026 iThome 鐵人賽

DAY 28
1
Software Development

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

Day 28|如何重構一套已經沒人敢碰的測試框架:實戰重構藍圖

  • 分享至 

  • xImage
  •  

「面對一套爛掉的測試框架,最衝動的作法是直接宣告『我要全部砍掉重寫』;而最成熟的作法,是像進行心臟外科手術一樣,在系統持續執行的同時,完成器官置換與血管重構。」

大家好,我是 Jane,一個每天在第一線處理跨平台(Web & App)自動化測試、跟 CI/CD Pipeline 與各種歷史遺留架構(Legacy Framework)奮戰的自動化測試工程師(SDET)。

在上集 Day 27 中,我們剖析了自動化測試技術債產生的 7 大坑洞,並提出了漸進式清債的理念。

但在現實職場中,我們最常面臨的硬核挑戰往往是:

  • 「你剛開工接手了一個專案,裡面的自動化測試框架是兩年前離職的前輩寫的。程式碼有 5000 行,完全沒有文件,裡面混合了寫死的帳密、幾百個 time.sleep,CI 上天天紅綠燈交替。團隊的人都對你說:『那個測試很敏感,沒事別去動它!』」

當一套測試框架變成「誰動誰倒楣」的黑盒子時,我們該如何系統化地重構它,讓它重新獲得團隊的信任?

今天這篇文章,我們就來手把手制定一套 第一線 SDET 的舊測試框架重構 5 階段藍圖(Refactoring Blueprint)

一、 重構 5 階段藍圖:切忌盲目「砍掉重寫」

為什麼不能直接砍掉重寫?因為舊框架雖然爛,但它裡面依然包含著過去兩年團隊累積的商業邊界邏輯(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)

階段 1:建立現況數據防線(Establish Baseline Metrics)

  • 目標:在動任何一行程式碼之前,先用數據記錄「現狀有多爛」。
  • 行動:連續執行全套測試 5 次,算出三項指標:通率過(Pass Rate)、Flaky 案例比例(Flaky Rate)以及全套執行總時間。這將作為你後續重構成果的基準(Baseline)。

階段 2:修復執行與報告能力(Fix Runner & Reporting)

  • 目標:讓測試能被「安定地執行」並「清晰地產出失敗證據」。
  • 行動
    1. 導入 pytest-htmlAllure Report,並加入 Day 25 的失敗自動截圖與 Trace 鉤子。
    2. 暫時將排查後確認無法維護且天天 Flaky 的案例打上 @pytest.mark.quarantine 標籤移出 CI 門檻。

階段 3:分離測試資料與環境組態(Decouple Data & Config)

  • 目標:消滅程式碼中硬編碼的網址、帳號密碼與寫死的 https://...
  • 行動:建立 config/settings.py.env 機制,將環境設定抽離;改用動態 UUID 或 API 產生測試帳號。

階段 4:抽離通用生命週期與 Hook(Standardize Fixtures)

  • 目標:消滅每個測試檔案開頭複製貼上的 30 行 Setup/Teardown 程式碼。
  • 行動:將瀏覽器初始化、API Client 登入、 Cookies 清理等邏輯全數下沉至 tests/conftest.py

階段 5:逐步模組化 Page / Component Objects(Modularize)

  • 目標:消滅測試案例中的 DOM 定位器(如 page.click("div > ul > li:nth-child(2)"))。
  • 行動:利用 Day 9 / Day 10 的 Page & Component Object 模式,每次只重構當前正在維護的頁面區塊。

二、 Python / pytest 實戰:舊代碼手術示範

我們來看一段真實的舊代碼,以及如何透過上述藍圖逐步將其手術重構:

❌ 舊框架代碼:滿是硬編碼、重複初始化與死板等待(階段 0 現狀)

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

⭕ 階段 3 ~ 5 重構後:極致簡潔、高可讀性且無資料衝突

第一步:抽離全域 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("已加入購物車")

三、 第一線 SDET 的重構「心理建設與溝通」

重構舊框架不只是技術問題,更是團隊信任問題。在重構期間,請記住以下 3 個溝通心法:

  1. 每週向團隊展示「數據改善(Metrics Improvement)」
    製作簡單的對比圖表向 PM 與主管匯報:

    • 「本週完成了 Checkout 模組的重構:執行時間從 18 分鐘 降至 3 分鐘,Flaky 失敗率從 25% 降至 0%。」

      用數據證明你正在讓團隊的開發變快,而不是在做無用功。

  2. 遵守「雙軌並行(Strangler Fig Pattern)」原則
    在重構初期,讓新的 pytest 專案結構與舊的 legacy 腳本在 CI 上並行運作。每當重構好一個模組,就把舊專案裡對應的案例關閉(Delete/Disable)。當舊專案的案例歸零時,重構即完美宣告結束!

  3. 讓開發工程師(Dev)參與 Review 新的架構
    主動邀請 Dev 來 Review 你的 conftest.pysettings.py。當 Dev 發現新的測試程式碼乾淨、易懂且可在 Local 快速跑過時,他們就會願意開始主動幫忙寫測試!

明日預告

告別了舊框架重構的硬核實務後,我們將邁入 2026 年軟體工程與自動化測試領域最熱門、也最不可忽視的時代議題:

「AI(LLM)到底可以怎麼幫助自動化測試?資深 SDET 該如何善用 AI 工具,同時避開 AI 產出虛假斷言的陷阱?」

明天 Day 29,我們將深入探討:《 Day 29|AI 可以怎麼幫助自動化測試:2026 年 SDET 的 AI 實戰指南 》


上一篇
Day 27|自動化測試技術債如何形成?7 大常見的防禦性坑洞與解法
下一篇
Day 29|AI可以怎麼幫助自動化測試:2026 年 SDET 的 AI 實戰指南
系列文
從脆弱腳本到可信任測試平台:自動化測試架構30天30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言