name: 测试数据生成 description: 使用工厂、固定装置和假数据库生成真实、一致的测试数据。用于测试数据、固定装置、模拟数据、伪造者、测试构建器和种子数据生成。
测试数据生成
概览
测试数据生成为自动化测试创建真实、一致且可维护的测试数据。设计良好的测试数据减少了测试的脆弱性,提高了可读性,并使得创建多样化的测试场景变得更加容易。
何时使用
- 创建集成测试的固定装置
- 为开发数据库生成假数据
- 构建具有复杂关系的测试数据
- 创建用于测试的真实用户输入
- 种子测试数据库
- 生成边缘情况和边界值
- 构建可重用的测试数据工厂
指令
1. 工厂模式用于测试数据
JavaScript/Jest 与工厂函数
// tests/factories/userFactory.js
const { faker } = require('@faker-js/faker');
class UserFactory {
static build(overrides = {}) {
return {
id: faker.string.uuid(),
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
age: faker.number.int({ min: 18, max: 80 }),
phone: faker.phone.number(),
address: {
street: faker.location.streetAddress(),
city: faker.location.city(),
state: faker.location.state(),
zip: faker.location.zipCode(),
country: 'USA'
},
role: 'user',
isActive: true,
createdAt: faker.date.past(),
...overrides
};
}
static buildMany(count, overrides = {}) {
return Array.from({ length: count }, () => this.build(overrides));
}
static buildAdmin(overrides = {}) {
return this.build({
role: 'admin',
permissions: ['read', 'write', 'delete'],
...overrides
});
}
static buildInactive(overrides = {}) {
return this.build({
isActive: false,
deactivatedAt: faker.date.recent(),
...overrides
});
}
}
// tests/user.test.js
describe('User Service', () => {
test('should create user with valid data', () => {
const userData = UserFactory.build();
const user = userService.create(userData);
expect(user.email).toBe(userData.email);
expect(user.isActive).toBe(true);
});
test('should handle admin users differently', () => {
const admin = UserFactory.buildAdmin();
expect(admin.role).toBe('admin');
expect(admin.permissions).toContain('delete');
});
test('should process multiple users', () => {
const users = UserFactory.buildMany(5);
expect(users).toHaveLength(5);
expect(new Set(users.map(u => u.email)).size).toBe(5); // 所有唯一
});
});
Python 与 Factory Boy
# tests/factories.py
import factory
from factory.faker import Faker
from datetime import datetime, timedelta
from app.models import User, Order, Product
class UserFactory(factory.Factory):
class Meta:
model = User
id = factory.Sequence(lambda n: n)
email = Faker('email')
first_name = Faker('first_name')
last_name = Faker('last_name')
username = factory.LazyAttribute(
lambda obj: f"{obj.first_name.lower()}.{obj.last_name.lower()}"
)
age = Faker('random_int', min=18, max=80)
phone = Faker('phone_number')
is_active = True
role = 'user'
created_at = Faker('date_time_this_year')
class Params:
# 不同用户类型的特质
admin = factory.Trait(
role='admin',
permissions=['read', 'write', 'delete']
)
inactive = factory.Trait(
is_active=False,
deactivated_at=factory.LazyFunction(datetime.now)
)
premium = factory.Trait(
subscription='premium',
subscription_end=factory.LazyFunction(
lambda: datetime.now() + timedelta(days=365)
)
)
class ProductFactory(factory.Factory):
class Meta:
model = Product
id = factory.Sequence(lambda n: n)
name = Faker('commerce_product_name')
description = Faker('text', max_nb_chars=200)
price = Faker('pydecimal', left_digits=3, right_digits=2, positive=True)
sku = factory.LazyAttribute(
lambda obj: f"SKU-{obj.id:06d}"
)
stock = Faker('random_int', min=0, max=100)
category = Faker('random_element', elements=['electronics', 'clothing', 'books'])
is_available = factory.LazyAttribute(lambda obj: obj.stock > 0)
class OrderFactory(factory.Factory):
class Meta:
model = Order
id = factory.Sequence(lambda n: n)
user = factory.SubFactory(UserFactory)
status = 'pending'
total = Faker('pydecimal', left_digits=4, right_digits=2, positive=True)
created_at = Faker('date_time_this_month')
@factory.post_generation
def products(self, create, extracted, **kwargs):
"""创建后添加产品到订单。"""
if not create:
return
if extracted:
for product in extracted:
self.products.add(product)
else:
# 默认添加1-3个随机产品
count = kwargs.get('count', 3)
self.products.add(*ProductFactory.build_batch(count))
# tests/test_orders.py
import pytest
from tests.factories import UserFactory, OrderFactory, ProductFactory
def test_create_order_with_products():
"""测试带有特定产品的订单创建。"""
products = ProductFactory.build_batch(3)
order = OrderFactory.build(products=products)
assert order.user is not None
assert len(order.products) == 3
assert order.status == 'pending'
def test_admin_user_permissions():
"""测试管理员用户具有正确的权限。"""
admin = UserFactory.build(admin=True)
assert admin.role == 'admin'
assert 'delete' in admin.permissions
def test_inactive_user():
"""测试非活跃用户属性。"""
user = UserFactory.build(inactive=True)
assert not user.is_active
assert user.deactivated_at is not None
def test_bulk_user_creation():
"""测试一次性创建多个用户。"""
users = UserFactory.build_batch(10, role='user')
assert len(users) == 10
assert all(u.role == 'user' for u in users)
# 所有电子邮件应该是唯一的
assert len(set(u.email for u in users)) == 10
2. 构建器模式用于复杂对象
// tests/builders/OrderBuilder.ts
import { faker } from '@faker-js/faker';
export class OrderBuilder {
private order: Partial<Order> = {
id: faker.string.uuid(),
status: 'pending',
items: [],
total: 0,
createdAt: new Date(),
};
withId(id: string): this {
this.order.id = id;
return this;
}
withStatus(status: OrderStatus): this {
this.order.status = status;
return this;
}
withUser(user: User): this {
this.order.userId = user.id;
this.order.user = user;
return this;
}
withItems(items: OrderItem[]): this {
this.order.items = items;
this.order.total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
return this;
}
addItem(product: Product, quantity: number = 1): this {
const item: OrderItem = {
productId: product.id,
product,
quantity,
price: product.price,
subtotal: product.price * quantity,
};
this.order.items = [...(this.order.items || []), item];
this.order.total = (this.order.total || 0) + item.subtotal;
return this;
}
withShippingAddress(address: Address): this {
this.order.shippingAddress = address;
return this;
}
asPaid(): this {
this.order.status = 'paid';
this.order.paidAt = new Date();
return this;
}
asShipped(): this {
this.order.status = 'shipped';
this.order.shippedAt = new Date();
return this;
}
build(): Order {
return this.order as Order;
}
}
// 测试中的使用
describe('Order Processing', () => {
it('should calculate total correctly', () => {
const product1 = ProductBuilder.aProduct().withPrice(10.00).build();
const product2 = ProductBuilder.aProduct().withPrice(25.00).build();
const order = new OrderBuilder()
.withUser(UserBuilder.aUser().build())
.addItem(product1, 2) // $20
.addItem(product2, 1) // $25
.build();
expect(order.total).toBe(45.00);
expect(order.items).toHaveLength(2);
});
it('should process paid orders', () => {
const order = new OrderBuilder()
.withUser(UserBuilder.aUser().build())
.addItem(ProductBuilder.aProduct().build())
.asPaid()
.build();
expect(order.status).toBe('paid');
expect(order.paidAt).toBeDefined();
});
});
3. 集成测试的固定装置
Jest/TypeScript 与数据库固定装置
// tests/fixtures/database.ts
import { PrismaClient } from '@prisma/client';
import { UserFactory, ProductFactory, OrderFactory } from './factories';
export class DatabaseFixtures {
constructor(private prisma: PrismaClient) {}
async seed() {
// 创建用户
const users = await Promise.all(
UserFactory.buildMany(10).map(userData =>
this.prisma.user.create({ data: userData })
)
);
// 创建产品
const products = await Promise.all(
ProductFactory.buildMany(20).map(productData =>
this.prisma.product.create({ data: productData })
)
);
// 创建订单
const orders = await Promise.all(
OrderFactory.buildMany(15).map(orderData =>
this.prisma.order.create({
data: {
...orderData,
userId: users[Math.floor(Math.random() * users.length)].id,
items: {
create: products.slice(0, 3).map(product => ({
productId: product.id,
quantity: Math.floor(Math.random() * 3) + 1,
price: product.price,
})),
},
},
})
)
);
return { users, products, orders };
}
async clear() {
await this.prisma.orderItem.deleteMany();
await this.prisma.order.deleteMany();
await this.prisma.product.deleteMany();
await this.prisma.user.deleteMany();
}
}
// tests/setup.ts
import { PrismaClient } from '@prisma/client';
import { DatabaseFixtures } from './fixtures/database';
const prisma = new PrismaClient();
const fixtures = new DatabaseFixtures(prisma);
beforeAll(async () => {
await fixtures.clear();
await fixtures.seed();
});
afterAll(async () => {
await fixtures.clear();
await prisma.$disconnect();
});
pytest 固定装置
# tests/conftest.py
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from tests.factories import UserFactory, ProductFactory, OrderFactory
@pytest.fixture(scope='session')
def engine():
"""创建数据库引擎。"""
return create_engine('sqlite:///:memory:')
@pytest.fixture(scope='session')
def tables(engine):
"""创建所有表。"""
Base.metadata.create_all(engine)
yield
Base.metadata.drop_all(engine)
@pytest.fixture
def db_session(engine, tables):
"""为每个测试创建数据库会话。"""
Session = sessionmaker(bind=engine)
session = Session()
yield session
session.rollback()
session.close()
@pytest.fixture
def sample_users(db_session):
"""为测试创建样本用户。"""
users = UserFactory.build_batch(5)
db_session.add_all(users)
db_session.commit()
return users
@pytest.fixture
def sample_products(db_session):
"""为测试创建样本产品。"""
products = ProductFactory.build_batch(10)
db_session.add_all(products)
db_session.commit()
return products
@pytest.fixture
def admin_user(db_session):
"""创建管理员用户。"""
admin = UserFactory.build(admin=True)
db_session.add(admin)
db_session.commit()
return admin
@pytest.fixture
def order_with_items(db_session, sample_users, sample_products):
"""创建带有商品的订单。"""
order = OrderFactory.build(
user=sample_users[0],
products=sample_products[:3]
)
db_session.add(order)
db_session.commit()
return order
# 测试中的使用
def test_user_orders(order_with_items):
"""测试用户有正确的订单。"""
user = order_with_items.user
assert len(user.orders) == 1
assert user.orders[0].id == order_with_items.id
4. 真实数据生成
// tests/helpers/dataGenerator.js
const { faker } = require('@faker-js/faker');
class DataGenerator {
static generateCreditCard() {
return {
number: faker.finance.creditCardNumber('#### #### #### ####'),
cvv: faker.finance.creditCardCVV(),
expiry: faker.date.future().toISOString().slice(0, 7), // YYYY-MM
type: faker.helpers.arrayElement(['visa', 'mastercard', 'amex']),
};
}
static generateAddress() {
return {
street: faker.location.streetAddress(),
city: faker.location.city(),
state: faker.location.state(),
zip: faker.location.zipCode(),
country: faker.location.country(),
coordinates: {
lat: parseFloat(faker.location.latitude()),
lng: parseFloat(faker.location.longitude()),
},
};
}
static generateDateRange(days = 30) {
const endDate = new Date();
const startDate = new Date();
startDate.setDate(startDate.getDate() - days);
return { startDate, endDate };
}
static generateTimeSeries(count, interval = 'day') {
const data = [];
const now = new Date();
for (let i = count - 1; i >= 0; i--) {
const date = new Date(now);
if (interval === 'day') date.setDate(date.getDate() - i);
if (interval === 'hour') date.setHours(date.getHours() - i);
data.push({
timestamp: date,
value: faker.number.float({ min: 0, max: 100, precision: 0.01 }),
});
}
return data;
}
static generateRealisticEmail(firstName, lastName, domain = 'example.com') {
const patterns = [
`${firstName}.${lastName}`,
`${firstName}${lastName}`,
`${firstName.charAt(0)}${lastName}`,
`${firstName}_${lastName}`,
];
const pattern = faker.helpers.arrayElement(patterns);
return `${pattern.toLowerCase()}@${domain}`;
}
}
module.exports = { DataGenerator };
最佳实践
✅ 要做
- 使用伪造库生成真实数据
- 为常见对象创建可重用的工厂
- 通过覆盖使工厂灵活
- 在需要的地方生成唯一值(电子邮件、ID)
- 使用构建器构建复杂对象
- 为集成测试设置创建固定装置
- 生成边缘情况(空字符串、空值、边界)
- 尽可能保持测试数据的确定性
❌ 不要做
- 在多个地方硬编码测试数据
- 在测试中使用生产数据
- 为可重现的测试生成真正随机的数据
- 创建过于复杂的工厂层级
- 忽略数据关系和约束
- 为简单测试生成大量数据集
- 忘记清理生成的数据
- 所有测试使用相同的测试数据
工具 & 库
- JavaScript: @faker-js/faker, fishery, rosie, casual
- Python: factory_boy, faker, hypothesis
- Java: Instancio, EasyRandom, JavaFaker, Mockito
- Ruby: FactoryBot, Faker, Fabrication
- 数据库: SQL 固定装置,JSON 固定装置,CSV 导入
示例:完整的测试数据设置
// tests/setup/testData.ts
import { faker } from '@faker-js/faker';
// 为确定性测试配置伪造者
faker.seed(12345);
export const TestData = {
users: {
admin: () => ({
email: 'admin@test.com',
role: 'admin',
permissions: ['read', 'write', 'delete'],
}),
regular: () => ({
email: faker.internet.email(),
role: 'user',
isActive: true,
}),
},
products: {
inStock: (overrides = {}) => ({
name: faker.commerce.productName(),
price: parseFloat(faker.commerce.price()),
stock: faker.number.int({ min: 10, max: 100 }),
isAvailable: true,
...overrides,
}),
outOfStock: () => ({
...TestData.products.inStock(),
stock: 0,
isAvailable: false,
}),
},
orders: {
pending: (userId: string) => ({
userId,
status: 'pending',
items: [],
total: 0,
}),
completed: (userId: string) => ({
userId,
status: 'completed',
completedAt: faker.date.recent(),
items: [],
total: faker.number.float({ min: 10, max: 1000 }),
}),
},
};
示例
另见:集成测试、模拟桩、持续测试技能,有效使用测试数据。