「好的測試資料策略就像一場完美的魔術:在測試開始時精準變出所需的場景,在測試結束後不留半點痕跡。」
大家好,我是 Jane,一個每天在第一線處理跨平台(Web & App)自動化測試、跟 CI/CD Pipeline 與測試資料纏鬥的自動化測試工程師(SDET)。
在上集 Day 16 中,我們剖析了為什麼「測試資料」比測試程式碼更容易導致自動化測試失敗。我們知道了資料污染、順序依賴與環境不一致是破壞測試可信度的三大元兇。
今天我們要來解決最硬核的落地問題:在實際的 pytest 自動化框架中,我們到底該用什麼方式來建立測試資料?測試結束後又該如何乾淨地清理?
在 Web 與 App 自動化測試實務中,建立測試資料主要有以下 3 種策略:
┌─────────────────────────┐
│ 測試資料建立 3 大策略 │
└────────────┬────────────┘
│
┌───────────────────────────┼───────────────────────────┐
▼ ▼ ▼
1. 預先建立 (Pre-seeded DB) 2. UI 畫面一步步建立 3. API / Factory 動態建立
- 效能最高,但極易衝突 - 極慢且極其脆弱 (Flaky) - 兼具速度、獨立性與彈性
| 策略 | 做法 | 優點 | 缺點 | 建議使用時機 |
| 1. 靜態預建 (Pre-seeded) | 測試前在 Staging DB 寫死數十筆固定帳號與商品。 | 測試執行速度最快,不需初始化時間。 | 多人或多 Pipeline 平行執行時極易產生資料衝突與污染。 | 全站層級的唯讀基礎資料(如:國別列表、幣別選單)。 |
| 2. 純 UI 操作建立 | 在每一個案例開頭,透過 UI 按鈕一步步點擊建資料。 | 貼近真實使用者操作。 | 速度極慢,且前置步驟只要 UI 微調就會導致整條案例失敗。 | 強烈不建議! 除非該測試目標就是在驗證「建資料流程」本身。 |
| 3. API / Factory 動態建立 | 在 Arrange 階段直接呼叫後端 API,動態建立當次案例專屬資料。 | 速度快、資料 100% 獨立隔離、不受 UI 變動影響。 | 需要維護 API Client Helper 工具。 | 黃金標準(Best Practice)! 適用於 90% 以上的測試場景。 |
SDET 黃金法則:用 API 準備前置資料(Arrange),用 UI / App 驗證核心流程(Act & Assert)!
建立資料後,如何確保不把測試環境變成垃圾場?清理資料通常有以下 4 種方式:
yield 後執行 API/DB 刪除。test_user_a1b2c3),即使不刪除也不會影響下一次測試(適用於無法刪除資料的系統)。test_ 前綴的垃圾資料。我們結合 Factory Pattern(工廠模式) 與 pytest Fixture,展示一套兼具「動態生成、參數化與自動清理」的代碼架構:
utils/api_client.py)首先,封裝一個快速呼叫後端 API 建立資料的 Helper:
Python
# utils/api_client.py
import requests
from config.settings import settings
class APIClient:
def __init__(self):
self.base_url = settings.BASE_URL
self.headers = {"Authorization": f"Bearer {settings.API_TOKEN}"}
def create_user(self, user_data: dict) -> dict:
response = requests.post(
f"{self.base_url}/api/v1/users",
json=user_data,
headers=self.headers
)
response.raise_for_status()
return response.json() # 回傳包含 user_id 的 dict
def delete_user(self, user_id: str):
response = requests.delete(
f"{self.base_url}/api/v1/users/{user_id}",
headers=self.headers
)
# 清理邏輯允許 404 (代表已被刪除)
if response.status_code not in (200, 204, 404):
print(f"[Warning] 清理使用者 {user_id} 失敗: {response.text}")
tests/conftest.py)使用 Python 的 yield 語法,輕鬆實現 Setup > Execute > Teardown:
Python
# tests/conftest.py
import uuid
import pytest
from utils.api_client import APIClient
@pytest.fixture(scope="session")
def api_client():
return APIClient()
@pytest.fixture(scope="function")
def user_factory(api_client: APIClient):
"""
資料工廠 Fixture:允許測試案例依需求傳參建立多個使用者,並在測試後自動清理全部建立的資料
"""
created_user_ids = []
def _create_user(role: str = "regular", prefix: str = "test") -> dict:
unique_id = str(uuid.uuid4())[:8]
user_payload = {
"username": f"{prefix}_{role}_{unique_id}",
"email": f"{prefix}_{unique_id}@example.com",
"role": role,
"password": "SecurePass123!"
}
user = api_client.create_user(user_payload)
# 紀錄已建立的 ID 以供事後清理
created_user_ids.append(user["id"])
return user
# 交付工廠函式給 Test Case
yield _create_user
# Teardown 階段:測試案例結束後(無論 Pass/Fail)自動批次清理
for user_id in created_user_ids:
api_client.delete_user(user_id)
tests/test_checkout.py)測試案例碼極度乾淨、語意清晰,且 100% 具備資料隔離能力:
Python
# tests/test_checkout.py
from playwright.sync_api import Page, expect
def test_vip_user_checkout_discount(page: Page, user_factory):
"""
驗證 VIP 使用者結帳時享有折扣
"""
# 1. Arrange: 使用工廠動態建立一名 VIP 權限的使用者
vip_user = user_factory(role="vip", prefix="checkout_spec")
# 2. Act: 使用當次專屬的 VIP 使用者進行 UI 登入與操作
page.goto("https://staging.example.com/login")
page.get_by_test_id("username-input").fill(vip_user["username"])
page.get_by_test_id("password-input").fill("SecurePass123!")
page.get_by_test_id("login-btn").click()
page.goto("https://staging.example.com/checkout")
# 3. Assert: 驗證 VIP 折扣標籤正確顯示
expect(page.get_by_test_id("vip-discount-badge")).to_be_visible()
# 測試結束後,conftest.py 的 user_factory 會自動呼叫 API 刪除這個 vip_user!
將 API Client 作為自動化框架的核心基礎建設
千萬不要覺得「我是做 UI / App 自動化的,所以我不用寫 API 呼叫」。一個優秀的 SDET,框架裡一定有強大的 API Helper,用來快速為 UI / App 測試鋪路。
清理邏輯要具備容錯能力(Graceful Cleanup)
Teardown 階段的刪除邏輯(如 delete_user)必須加入 try-catch 或允許 404,絕對不能因為清理失敗而讓原本測試通過的案例變成紅燈。
無可避免地使用預建資料時,加上『唯讀(Read-only)』約束
對於那些建立成本極高、不得不預建在 DB 裡的資料(例如:包含幾千筆關聯資料的大型專案),限制測試案例只讀不寫。如果要修改,必須複製一份副本後再去修改副本。
掌握了測試資料的動態建立與清理策略後,下一個考驗我們架構能力的問題來了:
「在測試過程中,系統往往會依賴外部服務(如:簡訊驗證碼、第三方金流 Gateway、信用卡授權)。我們到底該直接呼叫真實服務,還是該使用 Mock / Stub / Service Virtualization?」
明天 Day 18,我們將深入研討:《 Day 18|自動化測試如何處理外部服務:Mock、Stub 與真實整合環境的抉擇 》。