iT邦幫忙

2026 iThome 鐵人賽

DAY 6
1
Software Development

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

Day 6|測試腳本與測試框架有什麼不同?從單一腳本到永續架構的演進之路

  • 分享至 

  • xImage
  •  

「寫出一支能跑通的測試腳本只需要半天;但要設計一套能讓團隊維護三年的測試框架,你需要考慮的是整個架構。」

大家好,我是 Jane。

在前 5 天,我們聊完了自動化測試的觀念、策略與價值衡量指標。從今天開始,我們要正式進入技術實務層面:如何從頭打造一套「高穩定度、低維護成本」的測試框架?

許多剛接觸自動化的 QA 常問我一個問題:

  • 「Jane,我用 Playwright / Selenium 寫了一個 .spec.ts 檔,裡面能自動打開瀏覽器、輸入帳號密碼、點擊登入、最後驗證頁面文字。這算不算是『測試框架』?」

我的答案通常是:「這叫『測試腳本(Test Script)』,還不叫『測試框架(Test Framework)』。」

當你的專案只有 3 個案例時,寫單一腳本非常快、非常直覺;但當案例膨脹到 50 個、100 個,且需要整合到 CI/CD 時,沒有框架支撐的腳本將會變成一場維護災難。今天這篇文章,我們就來聊聊這兩者到底差在哪裡?

一、 測試腳本(Script) vs. 測試框架(Framework)

我們用一個簡單的對比來看兩者的本質差異:

比較維度 測試腳本 (Test Script) 測試框架 (Test Framework)
關注焦點 「如何完成某個特定的操作與驗證」 「如何組織、執行、管理所有測試與資源」
代碼結構 線性順序(Linear),將環境設定、資料、步驟、驗證全塞在一起 分層架構(Layered),職責分離(Separation of Concerns)
資料處理 測試資料寫死(Hardcoded)在程式碼中 資料與邏輯分離,支援 Fixtures / Factory / 多環境配置
維護成本 頁面改版或流程變動時,需要逐一修改幾十個腳本 只需要修改單一層級或元件(如 Page Object 或 API Client)
規模化能力 僅適用於少數案例(< 10 個) 可支援數百上千個案例,並支援平行執行與 CI 整合

簡單來說:腳本是「劇本裡的一幕」,而框架是「整座劇院的運作系統」。

二、 當單一腳本演進成測試框架時,我們到底要處理哪些事?

如果把寫好的 10 支單一腳本重構成一套「可維護的測試框架」,我們至少需要抽離並處理以下 7 大核心要素

                    ┌─────────────────────────┐
                    │  自動化測試框架 7 大核心   │
                    └────────────┬────────────┘
                                 │
     ┌───────────────────────────┼───────────────────────────┐
     ▼                           ▼                           ▼
1. 測試結構與生命週期         3. 測試資料管理 (Data)      5. 統一斷言機制 (Assertion)
   (Structure & Hooks)     4. 共用操作封裝 (Flows)     6. 報告與 Log (Reporting)
2. 環境與設定 (Config)                                 7. 錯誤與例外處理 (Error)

1. 測試結構與生命週期(Test Structure & Lifecycle)

  • 單一腳本作法:每個腳本自己寫啟動瀏覽器、初始化 API、關閉連線。
  • 框架化作法:透過 BeforeAll / BeforeEach / AfterEach / AfterAll 等 Hook 機制,將測試的前置準備(Setup)與事後清理(Teardown)標準化。

以下是Pytest 中對應的四種 Hook 實現方式與範例

對應關係總覽

傳統 Hook 概念 Pytest Fixture 實現方式 說明
BeforeAll @pytest.fixture(scope="session")yield 之前 的程式碼 在所有測試開始前僅執行一次
BeforeEach @pytest.fixture(scope="function")yield 之前 的程式碼 在每個測試函式執行前執行一次
AfterEach @pytest.fixture(scope="function")yield 之後 的程式碼 在每個測試函式執行後執行一次
AfterAll @pytest.fixture(scope="session")yield 之後 的程式碼

(或 conftest.py 中的 pytest_sessionfinish) | 在所有測試結束後僅執行一次 |

範例:使用 conftest.py@pytest.fixture

底下展示如何在 Pytest 中完整實現這四個階段的初始化與清理工作。

conftest.py(測試設定檔)

Python

import pytest

# ==========================================
# 1. Session 級別(對應 BeforeAll & AfterAll)
# ==========================================
@pytest.fixture(scope="session", autouse=True)
def db_connection():
    # --- BeforeAll 階段 ---
    print("\n[BeforeAll] 建立全域資料庫連線...")
    connection = {"status": "connected"}

    yield connection  # 將資源提供給測試使用

    # --- AfterAll 階段 ---
    print("\n[AfterAll] 關閉全域資料庫連線...")
    connection["status"] = "disconnected"

# ==========================================
# 2. Function 級別(對應 BeforeEach & AfterEach)
# ==========================================
@pytest.fixture(scope="function")
def user_data():
    # --- BeforeEach 階段 ---
    print("\n  [BeforeEach] 為當前測試重置/準備乾淨的使用者資料")
    data = {"user_id": 1, "username": "alice"}

    yield data  # 傳遞資料給測試函式

    # --- AfterEach 階段 ---
    print("\n  [AfterEach] 清理當前測試產生的臨時資料與狀態")
    data.clear()

test_user.py(測試檔案)

Python

def test_user_login(user_data):
    print("    -> 正在執行 test_user_login")
    assert user_data["username"] == "alice"

def test_user_update(user_data):
    print("    -> 正在執行 test_user_update")
    user_data["username"] = "bob"
    assert user_data["username"] == "bob"

執行結果輸出

執行 pytest -s test_user.py-s 參數用於顯示 print 輸出):

Plaintext

[BeforeAll] 建立全域資料庫連線...

  [BeforeEach] 為當前測試重置/準備乾淨的使用者資料
    -> 正在執行 test_user_login
  [AfterEach] 清理當前測試產生的臨時資料與狀態
.
  [BeforeEach] 為當前測試重置/準備乾淨的使用者資料
    -> 正在執行 test_user_update
  [AfterEach] 清理當前測試產生的臨時資料與狀態
.
[AfterAll] 關閉全域資料庫連線...

額外補充:原生 Pytest Hooks (pytest_sessionfinish)

如果你真的需要直接監聽 Pytest 內部的系統 Hook(而不是透過 Fixture),可以在 conftest.py 中使用內建 Hook 函式:

Python

# conftest.py

def pytest_sessionstart(session):
    """最底層的 BeforeAll:pytest 會話啟動時觸發"""
    print("\n>>> Pytest Session 開始啟動")

def pytest_sessionfinish(session, exitstatus):
    """最底層的 AfterAll:pytest 會話完全結束時觸發"""
    print("\n>>> Pytest Session 結束,離開狀態碼:", exitstatus)

結語:在 Python / pytest 世界中,90% 以上的需求使用 @pytest.fixture(搭配 yield 和對應的 scope)即可優雅地處理生命週期 Hook!

2. 環境與組態配置(Configuration Management)

  • 單一腳本作法:網址寫死 https://staging.example.com,換到 Test/Dev 環境就要改 Code。
  • 框架化作法:將 base_url、timeout、api_key、browser_type 等設定抽離至獨立的配置文件(如 .envconfig.json),支援透過命令列切換環境。

3. 測試資料管理(Test Data Management)

  • 單一腳本作法:帳號密碼 test_user@gmail.com 直接 Hardcode 在程式碼內。
  • 框架化作法:採用 Data-Driven(資料驅動)模式,將測試資料抽離成 JSON/CSV,或透過 Fixtures/API 在測試執行前動態生成唯一資料。

4. 共用操作與業務流程封裝(Common Actions & Business Flows)

  • 單一腳本作法:每個需要「登入」的案例,都重複寫一遍輸入帳密與點擊送出的步驟。
  • 框架化作法:封裝登入邏輯(如 loginService.loginAsAdmin())或採用 Page Object Pattern(Day 9 會詳細討論),讓腳本只表達「測試意圖」,不寫底層操作細節。

5. 統一斷言與狀態驗證(Assertion & State Check)

  • 單一腳本作法:到處寫 assert title == "Home",失敗時只印出 AssertionError
  • 框架化作法:封裝語意化的 Custom Assertions,失敗時自動印出 context(例如:「預期 API 回傳 200,實際得到 500,Error Message: ...」)。

6. 測試報告與日誌記錄(Reporting & Logging)

  • 單一腳本作法:依靠 console.log() 或 Terminal 輸出,跑完就沒了。
  • 框架化作法:整合 Allure、HTML Reporter 或 JUnit XML,失敗時自動附上截圖 (Screenshot)、網絡請求 (Network Trace) 與主機 Log,並將結果傳送到 Slack/Teams。

7. 錯誤處理與自動恢復(Error Handling & Recovery)

  • 單一腳本作法:網路稍微抖動或元素慢了 0.5 秒,腳本直接崩潰中止。
  • 框架化作法:設計合理的 Timeout 機制、重試策略(Retry)、顯式等待(Explicit Wait)與 Quarantine(隔離區)機制,確保單一案例失敗不會影響整個 Pipeline 的執行。

三、 第一線 QA 的感悟:不要一開始就過度設計(Over-engineering)

看到這裡,你可能會想:「天啊,要做一套框架竟然要處理這麼多事?那我是不是要花兩個月把架構寫完美才能開始寫測試?」

千萬不要! 這是很多程式底子好的 QA 最常踩的陷阱:過度抽離與抽象化(Over-engineering)。

最健康的演進路線是「漸進式重構(Progressive Refactoring)」:

  1. Phase 1(0 ~ 5 個案例):先寫幾支直覺的腳本,確認操作可行。
  2. Phase 2(5 ~ 20 個案例):發現重複代碼(如登入、環境變數),開始抽離 Config 與 Base Page/Client。
  3. Phase 3(20 ~ 50 個案例):導入標準化測試報告、測試資料 Fixture 與 CI Pipeline 整合。
  4. Phase 4(> 50 個案例):優化平行執行(Parallel)、失敗隔離與動態環境清理。

好的測試框架是「長出來的」,而不是「一口氣畫出來的」。

明日預告

了解了框架需要處理的 7 大要素後,我們接下來要深入到「單一測試案例」的內部品質。

許多人在寫腳本時,喜歡把「登入 > 搜尋商品 > 加購物車 > 填寫地址 > 付款 > 驗證訂單」全寫在同一條測試案例裡,結果一旦失敗,完全不知道是卡在哪一步。

明天 Day 7,我們就來聊聊:《 Day 7|一個測試案例應該負責多少事情?論測試案例的粒度與獨立性 》


上一篇
Day 5|如何衡量自動化測試的價值:從指標到團隊信任
下一篇
Day 7|一個測試案例應該負責多少事情?論測試案例的粒度與獨立性
系列文
從脆弱腳本到可信任測試平台:自動化測試架構30天7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言