> 九號工具站
返回列表

Test Data Management — Fixture / Factory / Seed / Cleanup 完整策略

自動化測試最大坑:test data 管理。Fixture vs Factory vs Mother、Seed 策略、cleanup 模式、跨環境資料、敏感資料處理。附 pytest / Playwright 範例。

QA test-data fixture factory seed automation
· 最後更新 2026-06-12 · 最後審閱 2026-08-30

💡 本文原刊於 qa.9niche.com,2026-08 併入 9niche.com 懶人包,內容照原文完整搬遷。

1. 前言

「自動化測試跑兩個禮拜後 staging DB 變垃圾場」是 99% 團隊的痛。Test data 管理沒做好、自動化壽命 = 半年。這篇給你 6 種策略 + 完整實作。

2. 為什麼 Test Data 是頭號殺手

Test data 不管理 = flaky test → 不信任 → 棄置

flowchart TD Auto[寫了自動化] --> P1[第 1 週<br>全綠] P1 --> P2[第 4 週<br>10% flaky] P2 --> P3[第 12 週<br>30% flaky + 重複帳號] P3 --> P4[第 24 週<br>沒人信任、棄置] P3 --> Why{為什麼?} Why --> R1[Test data 殘留] Why --> R2[Test 互打] Why --> R3[依賴順序] Why --> R4[資料污染] style Auto fill:#06b6d4,color:#fff style P4 fill:#ef4444,color:#fff style Why fill:#f59e0b,color:#fff

3. 6 種 Test Data 策略

每種有適合的場景。

mindmap root((Test Data<br>Strategies)) Fixture 靜態 yaml/json 適合穩定資料 Setup 用 Factory 動態產生 適合多變 每 test 新建 Seed Script DB 初始 Migration 後跑 適合 dev 環境 Snapshot Prod 抽樣 去識別化 適合整合測試 On-the-fly via API 用真實 API 建 最真實 最慢 Shared / Singleton 只建一次 慎用 適合 read-only

4. 策略 1: Fixture — 靜態檔案

適合:固定的 reference data(國家清單、產品分類)。

# tests/fixtures/users.yml
users:
  - id: 1
    email: [email protected]
    role: admin
  - id: 2
    email: [email protected]
    role: user
# pytest
@pytest.fixture
def users(scope="session"):
    with open('tests/fixtures/users.yml') as f:
        return yaml.safe_load(f)['users']

def test_admin_can_see_panel(users):
    admin = users[0]
    # ...

問題:多 test 共用 → 改一個壞一票。

5. 策略 2: Factory — 動態產生(最強)

適合:每 test 新建獨立資料、避免共用衝突。

Python 用 factory_boy

# tests/factories.py
import factory
from app.models import User, Order

class UserFactory(factory.Factory):
    class Meta:
        model = User

    email = factory.Sequence(lambda n: f"qa-user-{n}@test.com")
    name = factory.Faker('name')
    role = "user"
    created_at = factory.LazyFunction(datetime.now)

class OrderFactory(factory.Factory):
    class Meta:
        model = Order

    user = factory.SubFactory(UserFactory)
    total = factory.Faker('pydecimal', positive=True, max_value=1000)
    status = "pending"
# test
def test_user_can_view_own_order():
    user = UserFactory()
    order = OrderFactory(user=user)
    # ...

def test_admin_view_all_orders():
    # 一行建 10 個 user 各 3 個 order
    users = UserFactory.create_batch(10)
    for u in users:
        OrderFactory.create_batch(3, user=u)

Playwright 也可以

// fixtures/factories.ts
import { faker } from '@faker-js/faker';

let userIdSeq = 0;
export function makeUser(overrides = {}) {
  userIdSeq++;
  return {
    email: `qa-user-${userIdSeq}-${Date.now()}@test.com`,
    name: faker.person.fullName(),
    role: 'user',
    ...overrides,
  };
}

export function makeOrder(user, overrides = {}) {
  return {
    user_id: user.id,
    total: faker.number.float({ min: 10, max: 1000, precision: 0.01 }),
    status: 'pending',
    ...overrides,
  };
}
test('user views own order', async ({ api, page }) => {
  const user = await api.post('/users', makeUser()).json();
  const order = await api.post('/orders', makeOrder(user)).json();
  // ...
});

關鍵:每個 test 自己的 user/data、不互打。

6. 策略 3: Seed Script — DB 初始

適合:dev / staging 環境的 reference data + 少量 sample data。

# 跑一次塞滿基本資料
npm run db:seed
# 或
python manage.py seed_test_data
# seeds/dev_seed.py
def seed():
    # Reference data
    Country.objects.bulk_create([
        Country(code='TW', name='台灣'),
        Country(code='JP', name='日本'),
    ])

    # Sample users
    for i in range(20):
        UserFactory(email=f'demo-{i}@example.com')

    # Sample orders
    OrderFactory.create_batch(100)

問題:Seed 只能跑一次、跑多次會重複。要設計成 idempotent。

7. 策略 4: Snapshot — Prod 抽樣(謹慎用)

適合:整合測試需要真實 data 結構與分佈。

必須去識別化的欄位

  • ✅ 姓名 → faker
  • ✅ Email → user_{id}@masked.com
  • ✅ 手機 / 身分證 → fake
  • ✅ 信用卡 → mask 中間 8 位
  • ✅ 地址 → fake
  • ✅ IP → 隨機

工具

  • pgreplay (PostgreSQL)
  • AWS DMS(雲端)
  • 自建 script + Faker library
flowchart LR Prod[Production DB] --> Snap[Snapshot] Snap --> Anon[去識別化<br>name → fake<br>email → masked] Anon --> Staging[Staging DB] style Prod fill:#ef4444,color:#fff style Anon fill:#f59e0b,color:#fff style Staging fill:#10b981,color:#fff

8. 策略 5: On-the-fly via API — 最真實

適合:E2E test、要走真實 flow。

test('register → verify email → login', async ({ page, api }) => {
  // 1. API 建 user (跟前端 register flow 一樣)
  const email = `qa-${Date.now()}@test.com`;
  await api.post('/auth/register', { email, password: 'Pass@123' });

  // 2. 從 mail server 拿 verification link
  const link = await getVerificationLink(email);

  // 3. 走 verify
  await page.goto(link);

  // 4. Login
  await page.goto('/login');
  await page.fill('[name=email]', email);
  await page.fill('[name=password]', 'Pass@123');
  await page.click('[type=submit]');

  // 5. Assert
  await expect(page).toHaveURL('/dashboard');
});

最真實、最慢。適合 critical flow。

9. 策略 6: Shared / Singleton — 共用一份(慎用)

適合:read-only 的 fixture(產品目錄)。

@pytest.fixture(scope='session')
def all_countries(db):
    # 整 session 只建一次
    return CountryFactory.create_batch(50)

禁忌:可改寫的資料絕對不要 session scope。

10. 命名規範(避免 prod data 跟 test data 混)

# ✅ 一眼看出是測試
email = f'qa-test-{uuid.uuid4()}@example.com'
name = f'QA_Test_User_{uuid.uuid4()}'

# ❌ 看起來像真用戶
email = '[email protected]'
name = 'Alice Chen'

好處

  1. 後台搜 qa-test- 一次 cleanup
  2. Customer support 不會誤打給測試 email
  3. Analytics 可以排除測試流量

11. Cleanup 策略

Strategy A: 每 test cleanup(pytest fixture)

@pytest.fixture
def fresh_user(api):
    user = api.post('/users', json=make_user()).json()
    yield user
    # Test 跑完自動清
    api.delete(f'/users/{user["id"]}')

問題:test fail 時 cleanup 也跑 → 看不到失敗瞬間的 data。

# 改成失敗時保留
@pytest.fixture
def fresh_user(api, request):
    user = api.post('/users', json=make_user()).json()
    yield user
    if request.node.rep_call.passed:
        api.delete(f'/users/{user["id"]}')
    else:
        print(f"⚠️ Keeping user {user['id']} for debugging")

Strategy B: Transactional(最乾淨)

@pytest.fixture
def db_session():
    connection = engine.connect()
    transaction = connection.begin()
    session = Session(bind=connection)
    yield session
    session.close()
    transaction.rollback()  # 一切都不留
    connection.close()

所有改動都 rollback。最乾淨。但 API 測試 / E2E 用不了(API 走 HTTP、不在同 transaction)。

Strategy C: 整 DB reset(CI 友善)

# CI 起 docker-compose 含 fresh DB
docker compose up -d db
python manage.py migrate
python manage.py seed_dev_data
npm run test:e2e

每次 CI 全新 DB → 0 污染。慢一點但乾淨

Strategy D: Nightly cron

-- 每晚 3am 清測試 user
DELETE FROM users WHERE email LIKE 'qa-test-%' AND created_at < NOW() - INTERVAL '24 hours';

備胎策略。

flowchart TD Strategy{Cleanup<br>策略} --> S1[Test 內 cleanup<br>finally / afterEach] Strategy --> S2[Fixture teardown<br>pytest fixture] Strategy --> S3[每天 cron job<br>清過期測試資料] Strategy --> S4[整 DB reset<br>每次 CI 跑] Strategy --> S5[Transactional<br>每 test 一個 transaction、跑完 rollback] style S1 fill:#06b6d4,color:#fff style S2 fill:#10b981,color:#fff style S3 fill:#a855f7,color:#fff style S4 fill:#f59e0b,color:#fff style S5 fill:#ef4444,color:#fff

12. 跨環境的 Test Data 策略

規則

  1. Local 用 seed + factory
  2. CI 用 fresh container
  3. Staging 用 anonymized prod snapshot
  4. Prod 絕對不寫測試資料
flowchart LR Env{環境} --> Local[Local Dev] Env --> CI[CI / Sandbox] Env --> Staging[Staging] Env --> Prod[Production] Local --> L1[Seed + Factory<br>盡量真實] CI --> C1[Fresh DB<br>每次 reset] Staging --> S1[Snapshot from prod<br>去識別化] Prod --> P1[❌ 不寫測試資料] style Prod fill:#ef4444,color:#fff

13. 敏感資料處理

Faker 範例

from faker import Faker
fake = Faker('zh_TW')

email = fake.email()        # [email protected]
name = fake.name()          # 王小明
address = fake.address()    # 台北市信義區...
phone = fake.phone_number() # 0912345678
ssn = fake.ssn()           # 假身分證
cc = fake.credit_card_number()  # 4242 4242 4242 4242
mindmap root((敏感資料)) 必去識別化 姓名 Email 手機 地址 身分證 信用卡 DOB 可用 fake Faker library Mockaroo 自建 generator 法規 GDPR 個資法 PCI-DSS HIPAA

14. 測試卡號(信用卡測試專用)

Visa: 4111 1111 1111 1111
Visa (Stripe): 4242 4242 4242 4242
Mastercard: 5555 5555 5555 4444
Amex: 3782 822463 10005
過期卡: 4000 0000 0000 0069
扣款失敗: 4000 0000 0000 0002
3DS 必驗: 4000 0000 0000 3220

15. 反模式

flowchart TD Anti[Test Data 反模式] --> A1["共用同一個 test user"] Anti --> A2["把真實 email 寫進 test code"] Anti --> A3["在 prod 跑 test"] Anti --> A4["從不 cleanup"] Anti --> A5["每 test 都 reset 整 DB"] Anti --> A6["test data 寫死、不能改"] Anti --> A7["敏感資料用真實的"] Anti --> A8["順序依賴:必須先跑 test A 才能跑 B"] style A1 fill:#ef4444,color:#fff style A2 fill:#ef4444,color:#fff style A3 fill:#ef4444,color:#fff style A4 fill:#ef4444,color:#fff style A5 fill:#ef4444,color:#fff style A6 fill:#ef4444,color:#fff style A7 fill:#ef4444,color:#fff style A8 fill:#ef4444,color:#fff

16. 工具地圖

工具用途語言
factory_boyPython factoryPython
Faker假資料 generatorMulti
Mockaroo線上產假資料Web
fisheryTS factoryTypeScript
factories.ts 手寫TS 手刻TypeScript
Test ContainersDocker 起測試 DBMulti
db-fixturesDjango fixturePython
factory-girl (TS)TS factoryTypeScript

17. QA Lead 該推的 3 件事

  1. Test data 規範寫進 onboarding(每個新 QA 進來都讀)
  2. Cleanup strategy 跟 dev 對齊(DB schema 改了 fixture 也要改)
  3. Test data dashboard — 多少測試 user / 還在 DB 多少(看健康)

18. 給 QA 的 5 句

  1. 第一週做 test data 規範、後面省 6 個月維護
  2. 用 factory > fixture > snapshot > shared
  3. 命名加 prefix(qa-test-)救你的人生
  4. 失敗時別 cleanup、好用
  5. 永遠不要在 prod 寫 test data

19. 最後

Test data 是自動化的隱形主角。寫得好沒人看見、寫不好整個 team 都受害。從今天起把所有 test data 加 qa-test- prefix、每個 test 自己的 factory、cleanup 寫進 fixture — 三個月後你的自動化會從「2 週後不能跑」變「3 年後還在跑」。

ℹ️

一般聲明

本站提供之資訊僅供參考,不保證其完整性與正確性。使用者應自行判斷資訊之適用性。

意見反饋