寫測試的時候,準備假資料這件事本身就是一種負擔。
要測一個使用者系統,你得先建一個 User 物件,填上 id、email、name、created_at……如果測的是訂單,還要一個 Order,裡面還有 OrderItem 的 list。手刻這些東西花的時間,常常比寫測試邏輯本身還多。
polyfactory 解決這個問題:給它一個有 type hint 的 class,它自動生成符合型別的假資料。
安裝
1
| pip install polyfactory
|
如果用 Pydantic:
1
| pip install polyfactory pydantic
|
基本用法:dataclass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| from dataclasses import dataclass
from polyfactory.factories import DataclassFactory
@dataclass
class User:
id: int
name: str
email: str
is_active: bool
class UserFactory(DataclassFactory):
__model__ = User
user = UserFactory.build()
# User(id=42, name='vDjhqXt', email='KpLmn@example.com', is_active=True)
|
每次 build() 都生成不同的值,id 是隨機整數,email 是隨機字串,is_active 隨機 True/False。
Pydantic v2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| from pydantic import BaseModel
from polyfactory.factories.pydantic_factory import ModelFactory
class Order(BaseModel):
id: int
amount: float
status: str
items: list[str]
class OrderFactory(ModelFactory):
__model__ = Order
order = OrderFactory.build()
# Order(id=7, amount=3.14, status='aBcD', items=['x', 'y'])
|
Pydantic 的 validator 也會跑,polyfactory 不會生出不合規格的資料。
覆寫特定欄位
大部分情況你只在乎某幾個欄位,其他的不重要——這時候直接傳進去:
1
2
3
4
5
6
7
| # 只指定 status,其他欄位自動填
order = OrderFactory.build(status="paid")
# 或在 Factory class 裡設定預設值
class PaidOrderFactory(OrderFactory):
status = "paid"
amount = 100.0
|
這是我最常用的模式:用 base factory 生成大部分的資料,針對測試情境覆寫關鍵欄位,不用每次都 hardcode 整個物件。
批次生成
1
2
| users = UserFactory.batch(10)
# 一次給你 10 個不同的 User
|
測試需要一個 list 或要測分頁邏輯時特別好用。
搭配 pytest fixture
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # conftest.py
import pytest
from polyfactory.factories import DataclassFactory
@pytest.fixture
def user():
return UserFactory.build()
@pytest.fixture
def active_user():
return UserFactory.build(is_active=True)
@pytest.fixture
def users():
return UserFactory.batch(5)
|
fixture 直接回傳 factory 生成的物件,測試裡不需要知道 User 有哪些欄位——只有你真正在乎的欄位需要指定。
1
2
3
4
5
6
7
| def test_deactivate_user(active_user):
deactivate(active_user)
assert not active_user.is_active
def test_list_users(users):
result = get_user_list(users)
assert len(result) == 5
|
TypedDict 和 attrs
1
2
3
4
5
6
7
8
9
10
11
12
13
| from typing import TypedDict
from polyfactory.factories import TypedDictFactory
class Config(TypedDict):
host: str
port: int
debug: bool
class ConfigFactory(TypedDictFactory):
__model__ = Config
config = ConfigFactory.build()
# {'host': 'abc', 'port': 8080, 'debug': False}
|
巢狀物件
polyfactory 自動遞迴處理巢狀型別:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| @dataclass
class Address:
city: str
country: str
@dataclass
class User:
name: str
address: Address # 巢狀,自動生成
class UserFactory(DataclassFactory):
__model__ = User
user = UserFactory.build()
# user.address 也是自動生成的 Address 物件
|
不用分開建 AddressFactory 再傳進去,省了一層。
跟 Faker 的差別
Faker 也可以生假資料,但它的使用方式是「告訴我要什麼」:
1
2
3
4
| from faker import Faker
fake = Faker()
email = fake.email()
name = fake.name()
|
polyfactory 是「給我一個 class,我幫你填」:
1
| user = UserFactory.build()
|
兩者不衝突,但用途不同:
- Faker:你要控制每個欄位長什麼樣子(逼真的 email、真實城市名等)
- polyfactory:你只在乎型別正確,不在乎具體值
測試邏輯通常不在乎 email 是不是真實格式,只要是個字串就好——這種情況 polyfactory 的寫法更簡單。
小結
polyfactory 的核心概念就一個:型別就是規格,factory 照著型別生資料。
配合 pytest 的 fixture 用,測試準備資料這件事基本上可以降到一兩行,把注意力放在測試邏輯本身。
參考資源