「寫出一套漂亮的測試程式碼只要一週;但維護一套乾淨、可重複執行的測試資料庫,卻要跟整個團隊的開發習慣與系統狀態纏鬥一整年。」
大家好,我是 Jane,一個每天在第一線處理跨平台(Web & App)自動化測試、跟 CI/CD Pipeline 與測試環境奮戰的自動化測試工程師(SDET)。
恭喜大家跟著我完成了第三部分關於 UI 抗脆性與 Flaky test 治理的討論!
從今天開始,我們要跨入自動化測試最硬核、也最容易被忽略的領域:《第四部分:測試資料、環境與 API 整合》。
許多剛投入自動化的工程師常有一種錯覺:「只要把元素定位好、等待條件寫對,自動化測試就完成了。」
但到了實務中,不論是在 Web 端還是 App 端,導致 CI/CD 上測試爆紅燈的原因,常常根本不是測試程式碼壞掉,而是『測試資料(Test Data)』爛掉了!
今天這篇文章,我們就來好好聊聊:為什麼測試資料比測試程式碼更難管理?我們常踩到的資料陷阱有哪些?
測試程式碼是「無狀態(Stateless)」的——每次執行時,程式碼邏輯都不會自己改變。
但測試資料與環境卻是「有狀態(Stateful)」的——每一次測試執行、每一個 API 呼叫,都會在資料庫或系統中留下痕跡。
在 Web 與 App 自動化測試中,測試資料管理主要面臨以下 5 大致命陷阱:
┌─────────────────────────┐
│ 測試資料管理的 5 大陷阱 │
└────────────┬────────────┘
│
┌───────────────────────────┼───────────────────────────┐
▼ ▼ ▼
1. 帳號與狀態被共用與污染 3. 特殊商業狀態無法重建 5. 資料與環境不一致 (Env Drift)
2. 案例間存在執行順序依賴 4. 測試後遺留垃圾資料未清理
test_user_01。test_02_checkout.py 假設資料庫裡已經有 test_01_add_to_cart.py 建立好的購物車資料。test_02_checkout.py 時,測試立刻崩潰。我們來看一段在實務中最典型、也最脆弱的資料處理寫法:
Python
# tests/test_user_profile_brittle.py
from playwright.sync_api import Page, expect
def test_update_profile_brittle(page: Page):
page.goto("https://staging.example.com/login")
# 脆弱點 1: 硬編碼共用帳號!一旦別的案例修改了密碼,這個案例就死了
page.get_by_test_id("username").fill("vip_user_01")
page.get_by_test_id("password").fill("password123")
page.get_by_test_id("login-btn").click()
page.goto("https://staging.example.com/profile")
# 脆弱點 2: 修改了全域共用帳號的狀態,導致後續案例拿到被污染的姓名
page.get_by_test_id("nickname-input").fill("New_Nickname_Jane")
page.get_by_test_id("save-btn").click()
expect(page.get_by_test_id("toast-msg")).to_have_text("修改成功")
Python
# tests/conftest.py
import uuid
import pytest
from utils.api_client import APIClient # 假設的 API 工具
@pytest.fixture(scope="function")
def random_user(api_client: APIClient):
"""
動態產生獨立的測試使用者,測試結束後自動透過 API 清理
"""
# 1. Arrange: 動態生成唯一識別碼,確保每個測試案例的資料完全隔離
unique_id = str(uuid.uuid4())[:8]
user_data = {
"username": f"user_{unique_id}",
"email": f"test_{unique_id}@example.com",
"password": "SecurePassword123!"
}
# 直接透過 API 快速建立使用者狀態,不走昂貴的 UI 註冊流程
user = api_client.create_user(user_data)
# 將動態建立的 user 物件交付給 Test Case 使用
yield user
# 2. Teardown: 測試結束後清理資料,保持環境乾淨
api_client.delete_user(user["id"])
Python
# tests/test_user_profile_stable.py
from playwright.sync_api import Page, expect
def test_update_profile_stable(page: Page, random_user: dict):
"""
使用獨立的 random_user Fixture,完全不擔心與其他 Web/App 案例衝突
"""
page.goto("https://staging.example.com/login")
# 使用當次測試專用的獨立帳號
page.get_by_test_id("username").fill(random_user["username"])
page.get_by_test_id("password").fill(random_user["password"])
page.get_by_test_id("login-btn").click()
page.goto("https://staging.example.com/profile")
page.get_by_test_id("nickname-input").fill("Updated_Nickname")
page.get_by_test_id("save-btn").click()
expect(page.get_by_test_id("toast-msg")).to_have_text("修改成功")
身為同時把關 Web 與 App 品質的 SDET,在規劃測試資料時請務必守住這 3 個原則:
明白了測試資料管理的痛點與原則後,下一個核心問題隨之而來:
「在實務中,我們到底該用哪種策略來建立與清理測試資料?Fixture、Factory 還是 API 建立最好?」
明天 Day 17,我們將來深度探討:《 Day 17|測試資料的建立與清理策略:預建、API 產生與 Fixture 實戰 》。