以下是对“test-data-generation”技能的中文翻译和描述:
name: 测试数据生成 description: 使用Faker.js和类似工具进行合成测试数据的生成和管理。生成逼真的测试数据,创建数据工厂,实现数据库种子数据填充,和管理测试数据匿名化。 allowed-tools: Bash(*) 读写编辑 Grep WebFetch metadata: author: babysitter-sdk version: “1.0.0” category: 测试数据管理 backlog-id: SK-015
测试数据生成
你是测试数据生成 - 一个专门用于合成测试数据生成和管理的专业技能,提供创建逼真、可复现的测试数据的能力。
概览
这项技能使得AI驱动的测试数据管理成为可能,包括:
- 使用Faker.js生成逼真的测试数据
- 创建数据工厂和构建器
- 数据库种子脚本
- 测试数据匿名化和掩码
- 生成边界值测试数据
- 配置数据清理策略
- 使用种子创建确定性测试数据
- 与ORM工厂集成(Fishery, Factory Bot)
先决条件
- Node.js或Python环境
- 安装Faker库(@faker-js/faker 或 faker-python)
- 数据库访问权限,用于种子操作
- 可选:ORM(Prisma, Sequelize, SQLAlchemy)用于工厂集成
能力
1. 基础数据生成
使用Faker.js生成逼真的测试数据:
import { faker } from '@faker-js/faker';
// 生成用户数据
const generateUser = () => ({
id: faker.string.uuid(),
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
phone: faker.phone.number(),
address: {
street: faker.location.streetAddress(),
city: faker.location.city(),
state: faker.location.state(),
zipCode: faker.location.zipCode(),
country: faker.location.country()
},
company: faker.company.name(),
jobTitle: faker.person.jobTitle(),
avatar: faker.image.avatar(),
createdAt: faker.date.past(),
updatedAt: faker.date.recent()
});
// 生成多个用户
const users = faker.helpers.multiple(generateUser, { count: 100 });
2. 数据工厂模式
创建可重用的数据工厂:
import { faker } from '@faker-js/faker';
// 用户工厂
class UserFactory {
static defaults = {
id: () => faker.string.uuid(),
email: () => faker.internet.email(),
firstName: () => faker.person.firstName(),
lastName: () => faker.person.lastName(),
role: () => 'user',
isActive: () => true,
createdAt: () => faker.date.past()
};
static create(overrides = {}) {
const defaults = Object.fromEntries(
Object.entries(this.defaults).map(([key, fn]) => [key, fn()])
);
return { ...defaults, ...overrides };
}
static createMany(count, overrides = {}) {
return Array.from({ length: count }, () => this.create(overrides));
}
// 特征方法
static admin(overrides = {}) {
return this.create({ role: 'admin', ...overrides });
}
static inactive(overrides = {}) {
return this.create({ isActive: false, ...overrides });
}
}
// 使用方法
const user = UserFactory.create();
const admin = UserFactory.admin({ firstName: 'Admin' });
const users = UserFactory.createMany(50);
3. Fishery工厂(TypeScript)
使用Fishery进行类型化工厂:
import { Factory } from 'fishery';
import { faker } from '@faker-js/faker';
interface User {
id: string;
email: string;
firstName: string;
lastName: string;
role: 'user' | 'admin';
profile: Profile;
}
interface Profile {
bio: string;
avatar: string;
}
const profileFactory = Factory.define<Profile>(() => ({
bio: faker.person.bio(),
avatar: faker.image.avatar()
}));
const userFactory = Factory.define<User>(({ associations, sequence }) => ({
id: faker.string.uuid(),
email: faker.internet.email(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
role: 'user',
profile: associations.profile || profileFactory.build()
}));
// 使用方法
const user = userFactory.build();
const admin = userFactory.build({ role: 'admin' });
const usersWithProfiles = userFactory.buildList(10, {}, {
associations: { profile: profileFactory.build() }
});
4. 数据库种子填充
用测试数据填充数据库:
// seed.js - 数据库种子脚本
import { PrismaClient } from '@prisma/client';
import { faker } from '@faker-js/faker';
const prisma = new PrismaClient();
async function seed() {
// 设置种子以复现结果
faker.seed(12345);
// 清除现有数据
await prisma.order.deleteMany();
await prisma.product.deleteMany();
await prisma.user.deleteMany();
// 创建用户
const users = await Promise.all(
Array.from({ length: 50 }, () =>
prisma.user.create({
data: {
email: faker.internet.email(),
name: faker.person.fullName(),
password: faker.internet.password()
}
})
)
);
// 创建产品
const products = await Promise.all(
Array.from({ length: 100 }, () =>
prisma.product.create({
data: {
name: faker.commerce.productName(),
description: faker.commerce.productDescription(),
price: parseFloat(faker.commerce.price()),
sku: faker.string.alphanumeric(8).toUpperCase(),
inStock: faker.datatype.boolean()
}
})
)
);
// 创建订单
for (const user of users) {
const orderCount = faker.number.int({ min: 1, max: 5 });
for (let i = 0; i < orderCount; i++) {
await prisma.order.create({
data: {
userId: user.id,
status: faker.helpers.arrayElement(['pending', 'processing', 'shipped', 'delivered']),
total: parseFloat(faker.commerce.price({ min: 10, max: 500 })),
items: {
create: faker.helpers.arrayElements(products, { min: 1, max: 5 }).map(p => ({
productId: p.id,
quantity: faker.number.int({ min: 1, max: 3 }),
price: p.price
}))
}
}
});
}
}
console.log('种子填充完成!');
}
seed()
.catch(console.error)
.finally(() => prisma.$disconnect());
5. 边界值生成
生成边缘情况测试数据:
import { faker } from '@faker-js/faker';
const boundaryValues = {
// 字符串边界
strings: {
empty: '',
singleChar: 'a',
maxLength: 'a'.repeat(255),
unicode: '日本語テスト',
emoji: '🎉🚀💡',
specialChars: '<script>alert("xss")</script>',
sqlInjection: "'; DROP TABLE users; --",
whitespace: ' spaces ',
newlines: 'line1
line2\rline3'
},
// 数字边界
numbers: {
zero: 0,
negative: -1,
maxInt: Number.MAX_SAFE_INTEGER,
minInt: Number.MIN_SAFE_INTEGER,
decimal: 0.1 + 0.2, // 著名的浮点问题
infinity: Infinity,
nan: NaN
},
// 日期边界
dates: {
epochStart: new Date(0),
farPast: new Date('1900-01-01'),
farFuture: new Date('2100-12-31'),
leapYear: new Date('2024-02-29'),
endOfMonth: new Date('2024-01-31'),
timezoneEdge: new Date('2024-03-10T02:30:00') // 夏令时转换
},
// 数组边界
arrays: {
empty: [],
single: [1],
large: Array.from({ length: 10000 }, (_, i) => i)
}
};
// 生成边界测试用例
function generateBoundaryTestCases(schema) {
const testCases = [];
for (const [field, config] of Object.entries(schema)) {
if (config.type === 'string') {
testCases.push(
{ [field]: '', expected: config.required ? 'error' : 'success' },
{ [field]: 'a'.repeat(config.maxLength + 1), expected: 'error' },
{ [field]: 'a'.repeat(config.maxLength), expected: 'success' }
);
}
if (config.type === 'number') {
testCases.push(
{ [field]: config.min - 1, expected: 'error' },
{ [field]: config.min, expected: 'success' },
{ [field]: config.max, expected: 'success' },
{ [field]: config.max + 1, expected: 'error' }
);
}
}
return testCases;
}
6. 数据匿名化
为测试匿名化生产数据:
import { faker } from '@faker-js/faker';
import crypto from 'crypto';
const anonymize = {
// 一致匿名化(相同输入=相同输出)
email: (email) => {
const hash = crypto.createHash('md5').update(email).digest('hex').slice(0, 8);
return `user_${hash}@example.com`;
},
// 全部替换
name: () => faker.person.fullName(),
// 部分掩码
phone: (phone) => phone.replace(/\d(?=\d{4})/g, '*'),
// 格式保留
creditCard: (cc) => {
const last4 = cc.slice(-4);
return `****-****-****-${last4}`;
},
// 一致的假数据
ssn: (ssn) => {
faker.seed(crypto.createHash('md5').update(ssn).digest('hex'));
return faker.string.numeric('###-##-####');
},
// 地址匿名化
address: () => ({
street: faker.location.streetAddress(),
city: faker.location.city(),
state: faker.location.state(),
zip: faker.location.zipCode()
})
};
// 匿名化数据集
function anonymizeDataset(records) {
return records.map(record => ({
...record,
email: anonymize.email(record.email),
name: anonymize.name(),
phone: anonymize.phone(record.phone),
creditCard: record.creditCard ? anonymize.creditCard(record.creditCard) : null,
address: anonymize.address()
}));
}
7. 多语言支持
在不同语言环境中生成数据:
import { faker, Faker } from '@faker-js/faker';
import { de, fr, ja, es } from '@faker-js/faker';
// 德语环境
const fakerDE = new Faker({ locale: [de] });
const germanUser = {
name: fakerDE.person.fullName(),
address: fakerDE.location.streetAddress(),
city: fakerDE.location.city()
};
// 日语环境
const fakerJA = new Faker({ locale: [ja] });
const japaneseUser = {
name: fakerJA.person.fullName(),
address: fakerJA.location.streetAddress(),
city: fakerJA.location.city()
};
// 为多种语言环境生成测试数据
const locales = { de, fr, ja, es };
function generateMultiLocaleData(count = 10) {
return Object.entries(locales).flatMap(([code, locale]) => {
const localFaker = new Faker({ locale: [locale] });
return Array.from({ length: count }, () => ({
locale: code,
name: localFaker.person.fullName(),
email: localFaker.internet.email(),
phone: localFaker.phone.number(),
address: localFaker.location.streetAddress()
}));
});
}
8. 使用种子的确定性数据
创建可复现的测试数据:
import { faker } from '@faker-js/faker';
// 设置全局种子以复现
faker.seed(12345);
// 每次生成相同的数据
const user1 = faker.person.fullName(); // 总是相同的名字
const user2 = faker.person.fullName(); // 总是相同的名字
// 重置种子以获得新的序列
faker.seed(12345);
const user1Again = faker.person.fullName(); // 与user1相同
// 基于环境的种子设置
const testSeed = process.env.TEST_SEED || Date.now();
faker.seed(testSeed);
console.log(`使用种子: ${testSeed}`);
MCP服务器集成
这项技能可以利用以下MCP服务器增强能力:
| 服务器 | 描述 | 安装 |
|---|---|---|
| funsjanssen/faker-mcp | Faker.js MCP服务器 | GitHub |
最佳实践
- 使用种子 - 启用可复现的测试数据
- 工厂优于内联 - 使用工厂模式以维护性
- 逼真但安全 - 数据看起来真实但不应匹配真实人物
- 边界覆盖 - 在测试数据中包含边缘情况
- 清理 - 实施数据清理策略
- 性能 - 为大型数据集批量生成数据
- 验证 - 验证生成的数据符合预期模式
流程集成
这项技能与以下流程集成:
test-data-management.js- 测试数据管理的所有阶段e2e-test-suite.js- E2E测试数据设置api-testing.js- API测试数据生成environment-management.js- 环境数据种子填充
输出格式
执行操作时,提供结构化输出:
{
"operation": "generate",
"dataType": "users",
"count": 100,
"seed": 12345,
"locale": "en",
"schema": {
"id": "uuid",
"email": "email",
"name": "fullName"
},
"outputFile": "./test-data/users.json",
"statistics": {
"generated": 100,
"uniqueEmails": 100,
"executionTime": "45ms"
}
}
错误处理
- 生成前验证模式
- 处理大型数据集的内存限制
- 提供种子信息以调试
- 记录生成失败的上下文
- 支持部分数据生成恢复
约束
- 切勿使用真实个人数据作为种子
- 确保生成的电子邮件不匹配真实域名
- 避免生成可能被视为真实凭据的数据
- 遵守数据隐私法规(GDPR等)
- 记录种子值以测试可复现性