1. 测试数据生成这件事为什么值得单独拎出来聊做开发、做测试、做数据相关工作的朋友大概都经历过这样的场景功能写完了单元测试也跑了但一到集成测试或者演示环节就卡壳——数据库里空空如也或者只有几条“张三”“李四”这种一看就是手敲的假数据。更尴尬的是有时候线上出了bug想把那条出问题的数据复现一下却发现生产环境的数据根本不能直接拿来用脱敏又来不及。这就是测试数据生成要解决的问题。它不是什么高深的技术但绝对是那种“平时不觉得重要一旦缺了就寸步难行”的基础设施。我见过不少团队代码写得漂漂亮亮CI/CD流水线也搭得有模有样结果测试环境的数据全靠某个人手动往数据库里insert每次重建环境都要折腾大半天。这种隐性成本算下来其实非常吓人。传统做法无非几种手写SQL、用Faker这类库生成、或者找现成的工具。前两种灵活但费时第三种省事但往往不够贴合业务。而最近一两年AI的介入让这个领域出现了一些新玩法——你可以用自然语言描述你想要什么数据它直接给你生成出来连字段类型和业务约束都能帮你考虑进去。这篇内容就是想把这几条路都捋一遍。我会先讲清楚测试数据生成的核心诉求和设计思路然后拆解6款常用工具的实际用法和适用边界接着重点聊聊AI生成这条路怎么走、坑在哪里最后给一份问题排查速查表。不管你是刚接触测试数据的新人还是已经用过一些工具想看看有没有更优解的老手应该都能找到能直接抄作业的部分。2. 测试数据生成的核心诉求与方案选型逻辑2.1 什么样的数据才算“好”的测试数据很多人对测试数据的理解停留在“有数据就行”但实际用起来就会发现随便生成的数据和真正好用的数据之间差距巨大。我总结下来好的测试数据至少要满足四个维度。第一是结构正确。字段类型要对字符串不能塞进整型字段日期格式要符合数据库约束外键关联要能对得上。这听起来是废话但手写数据时最容易犯的就是这类低级错误尤其是表多了之后A表的user_id和B表的id对不上跑测试直接报外键约束失败。第二是业务合理。一个电商系统的订单数据金额不能是负数下单时间不能晚于发货时间商品数量不能是小数。这些业务规则如果靠随机生成很容易造出“看起来像数据但业务上不可能存在”的记录导致测试用例跑出来的结果没有参考价值。第三是分布真实。真实世界的数据往往不是均匀分布的。用户年龄可能集中在20到40岁订单金额可能符合长尾分布某些字段的取值可能高度集中在几个枚举值上。如果生成的数据全是均匀随机压力测试和性能分析的结果就会失真。第四是可复现。这一点经常被忽略。测试数据最好能通过一个种子seed确定性地生成这样今天跑出来的数据和明天跑出来的数据一致出了问题才能稳定复现。随机生成但不记录种子的做法在排查偶发bug时基本等于自断后路。2.2 工具选型时到底在权衡什么市面上测试数据生成工具不少但选型时真正需要权衡的维度其实就那么几个。学习成本是最直观的。有些工具需要你写Schema定义文件有些直接连数据库读表结构有些要你写代码调用API。团队里如果测试人员偏多、开发人员偏少那低代码甚至无代码的方案就更合适。数据质量是核心。能不能处理外键关联能不能自定义分布能不能保证唯一性约束这些直接决定了生成的数据能不能用。集成能力决定了它能不能融入现有流程。能不能输出SQL、CSV、JSON能不能直接写入数据库能不能在CI里跑这些决定了它是只能手动用一次还是能变成自动化流程的一部分。可扩展性则关系到长期使用。业务规则变了能不能方便地加自定义逻辑数据量大了性能跟不跟得上把这几个维度列出来之后你会发现没有哪个工具是全能的。手写代码最灵活但最费时现成工具省事但定制能力有限AI生成听起来很美但稳定性和可控性还需要验证。所以实际工作中往往是组合使用——用工具生成基础数据用代码补充业务逻辑用AI处理那些描述起来比写代码更快的场景。2.3 从手写到AI三代方案的演进逻辑如果把测试数据生成方案分代大概可以分成三个阶段。第一代是纯手工阶段。写SQL insert语句或者用Excel整理好再导入。这种方式最大的问题是不可维护——表结构一变所有脚本都要改数据量一大手写根本不现实。但它也有优点就是完全可控想生成什么就生成什么。第二代是程序化生成阶段。用Faker、Mockaroo这类库或工具通过定义规则来批量生成数据。这一代解决了效率和规模问题但规则还是要人来写业务逻辑复杂时规则本身可能比代码还难维护。第三代就是现在正在发生的AI辅助生成阶段。你用自然语言描述需求AI理解后直接产出数据或生成规则。这一代最大的价值在于降低了“描述需求”的门槛——你不用学DSL不用写正则直接说“给我生成100条北京地区25到35岁用户的订单记录金额在50到500之间下单时间集中在晚上”就行。但AI生成也有它的问题。一是不确定性同样的提示词两次生成的结果可能不一样二是可控性复杂约束下AI可能理解偏差三是数据安全把真实表结构甚至部分真实数据发给AI服务在很多场景下是不允许的。所以现阶段我的建议是AI适合做原型阶段的数据填充和规则草稿生成正式流程里还是要用确定性工具兜底。3. 六款常用工具的实际用法与适用边界3.1 Faker类库程序员的老朋友Faker应该是大多数人接触测试数据生成的第一站。Python有FakerJava有Java FakerJavaScript有faker.js基本主流语言都有对应实现。它的核心能力是提供大量“看起来像真的”的假数据——姓名、地址、电话、邮箱、公司名、日期等等。用法非常简单Python下装完库之后from faker import Faker fake Faker(zh_CN) for _ in range(5): print(fake.name(), fake.address(), fake.phone_number())几行代码就能批量产出中文姓名、地址和手机号。它支持locale切换zh_CN生成中文数据en_US生成英文数据做国际化测试时很方便。但Faker的边界也很明显。它生成的是通用假数据不理解你的业务。比如你要生成一个“订单状态”字段Faker没有这个概念你得自己从枚举值里随机选。外键关联它也不管你得自己维护ID的对应关系。所以Faker适合做基础字段填充业务逻辑部分还是要自己写代码补。实操心得Faker的seed方法可以固定随机种子Faker.seed(42)之后每次生成的结果一致。做需要复现的测试时一定要设种子不然出了问题只能干瞪眼。3.2 Mockaroo在线生成适合快速原型Mockaroo是一个在线服务打开网页就能用。它的交互很直观左边定义字段名和类型右边预览生成结果满意了直接下载CSV、JSON或SQL。它内置的类型比Faker丰富不少除了基础类型还有“信用卡号”“ISBN”“车牌号”这类特定格式。它比较实用的一个功能是支持自定义公式。比如你想让某个字段的值依赖另一个字段可以写类似this.price * 1.1的表达式。还支持从列表里随机取值做枚举字段很方便。不过Mockaroo是在线服务免费版有生成条数限制而且把表结构信息传到第三方平台这件事在很多公司是需要走安全审批的。所以它更适合个人项目或者非敏感数据的快速原型正式项目里用要谨慎。3.3 数据库原生工具贴近真实环境很多数据库自带数据生成能力比如MySQL可以用存储过程配合循环插入PostgreSQL有generate_series函数。这种方式最大的好处是数据直接落在数据库里不用导出导入而且可以利用数据库自身的约束和函数。举个例子PostgreSQL里生成1000条带随机日期的记录INSERT INTO orders (user_id, amount, created_at) SELECT (random() * 1000)::int, (random() * 500 50)::numeric(10,2), now() - (random() * interval 365 days) FROM generate_series(1, 1000);这种方式灵活度很高但可维护性差。SQL写长了之后很难读业务逻辑复杂时调试也麻烦。而且不同数据库语法差异大换一个数据库就要重写。它适合数据量不大、逻辑简单的场景或者作为其他工具的补充。3.4 专用数据生成平台企业级方案有一些专门做测试数据管理的平台功能比较全面。它们通常支持从数据库反向工程生成Schema然后基于Schema配置生成规则还能处理表之间的关联关系。有些还带数据脱敏功能可以把生产数据脱敏后用于测试。这类平台的优势是开箱即用和流程完整从数据生成到数据管理到数据清理都有覆盖。缺点是通常比较重部署和维护成本高小团队用起来可能觉得杀鸡用牛刀。而且很多是商业产品预算有限的团队要权衡一下。3.5 代码生成器从Schema直接出代码有一类工具是读取数据库表结构然后直接生成对应的实体类和测试数据构造代码。比如Java生态里的一些插件能根据表生成Entity、Mapper和对应的测试数据Builder。这种方式的好处是和代码库同步。表结构变了重新生成一次测试数据代码也跟着更新不容易出现字段对不上的情况。缺点是生成的代码往往比较模板化业务逻辑还是要自己补。而且它主要解决的是“结构正确”业务合理性还是要靠人。3.6 组合方案没有银弹只有组合拳实际项目里我见过用单一工具解决所有问题的团队基本都失败了。比较靠谱的做法是组合用Faker或Mockaroo生成基础字段用SQL处理批量插入和关联用代码补充业务规则用种子保证可复现。比如一个典型的用户订单场景可以这样分工用户基础信息用Faker生成订单和商品的关联用代码维护ID映射金额和时间的分布用自定义函数控制最后批量写入数据库。每一层做自己最擅长的事整体可控性和效率都比单工具方案好。4. AI生成测试数据新玩法与真实边界4.1 AI到底能帮上什么忙AI在测试数据生成上的价值我觉得主要体现在三个场景。场景一从自然语言到结构化数据。你直接说“生成20条员工记录包含姓名、部门、入职日期和月薪部门从技术、产品、运营里选月薪在8000到30000之间入职日期在最近三年内”AI能直接给你输出JSON或表格。这比写代码或配规则快得多尤其适合临时需要一批数据的情况。场景二生成复杂业务规则。有些业务规则用代码写很啰嗦但用自然语言描述很简洁。比如“生成一批订单要求每个用户的订单金额总和不超过他的账户余额且订单时间按先后顺序排列”。这种带约束的生成AI理解起来比传统工具容易。场景三数据格式转换和清洗。手里有一批格式不规范的数据想让AI帮忙整理成标准格式或者从一段描述里提取出结构化字段这些AI都很擅长。4.2 怎么写出靠谱的提示词用AI生成数据提示词的质量直接决定结果质量。我总结了一个比较实用的提示词结构分四块。第一块是角色和任务。开头明确说“你是一个测试数据生成器请生成……”让AI进入对应模式。第二块是数据结构。把字段名、类型、约束列清楚。最好用表格或JSON Schema的形式比纯文字描述准确。第三块是业务规则。把取值范围、关联关系、分布要求说清楚。比如“年龄字段服从正态分布均值30标准差5”。第四块是输出格式。明确要JSON、CSV还是SQL insert语句要不要带表头日期用什么格式。一个完整的例子请生成10条商品记录输出为JSON数组。 字段 - id: 整数从1开始递增 - name: 字符串中文商品名 - category: 枚举取值为[电子产品, 家居用品, 食品] - price: 浮点数保留两位小数范围10到2000 - stock: 整数范围0到500 - created_at: 日期时间格式YYYY-MM-DD HH:mm:ss在2024年内随机 要求电子产品价格偏高食品价格偏低。这种提示词出来的结果基本可以直接用不需要太多后处理。4.3 AI生成的三个硬伤和应对办法AI生成数据不是没有代价的实际用下来有三个问题比较突出。问题一是结果不稳定。同样的提示词两次生成的结果可能不一样。做需要复现的测试时这很致命。应对办法是让AI生成生成规则而不是直接生成数据。比如让它输出一段Python代码或一个配置模板你拿代码去跑这样每次结果都可控。问题二是复杂约束容易翻车。字段一多、关联一复杂AI就可能顾此失彼。比如要求“订单总额等于订单项金额之和”它可能算不对。应对办法是分步生成——先让AI生成主表数据再基于主表数据生成从表数据每一步都验证一下。问题三是数据安全。把真实表结构发给AI服务在很多公司是不合规的。应对办法是用脱敏后的Schema字段名可以改成通用的或者用本地部署的模型。如果都不行那就只把AI用在非敏感的原型阶段。注意不管用哪种AI服务都不要把生产环境的真实数据直接贴进去。表结构本身可能就包含敏感信息字段名往往能暴露业务逻辑。4.4 一个完整的AI辅助生成流程我实际用下来比较顺的流程是这样的。第一步整理Schema。把需要的表结构和字段约束整理成一份文档敏感字段名做替换。第二步让AI生成规则草稿。把Schema给AI让它输出一份生成规则的代码或配置。这一步不要求完美主要是拿个初稿。第三步人工审核和调整。检查AI生成的规则有没有逻辑错误业务约束有没有遗漏然后手动补上。第四步本地执行生成。用调整后的规则在本地跑生成实际数据。第五步验证数据质量。写几个校验查询检查外键关联、唯一性约束、业务规则是否满足。这个流程的好处是AI只负责它擅长的“理解需求并产出草稿”确定性和安全性由本地执行来保证。效率和可控性兼顾。5. 实操过程与核心环节实现5.1 环境准备与工具安装先说一下我这次实操用的环境。Python 3.10装了Faker库数据库用的PostgreSQL 14AI部分用的是本地部署的模型服务。如果你用在线AI服务把调用地址换一下就行。安装Fakerpip install faker数据库连接用psycopg2pip install psycopg2-binaryAI部分如果走API一般用requests就够了pip install requests环境准备好之后先建一个测试用的表结构。我拿一个简化的电商场景举例三张表用户表、商品表、订单表。CREATE TABLE users ( id SERIAL PRIMARY KEY, name VARCHAR(50) NOT NULL, email VARCHAR(100) UNIQUE NOT NULL, age INT CHECK (age 18 AND age 80), city VARCHAR(50), created_at TIMESTAMP DEFAULT now() ); CREATE TABLE products ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, category VARCHAR(20), price NUMERIC(10,2) CHECK (price 0), stock INT DEFAULT 0 ); CREATE TABLE orders ( id SERIAL PRIMARY KEY, user_id INT REFERENCES users(id), product_id INT REFERENCES products(id), quantity INT CHECK (quantity 0), amount NUMERIC(10,2), created_at TIMESTAMP DEFAULT now() );5.2 用Faker生成基础数据用户表的数据用Faker生成最合适因为姓名、邮箱、城市这些字段Faker都有现成的。from faker import Faker import psycopg2 import random fake Faker(zh_CN) Faker.seed(42) random.seed(42) conn psycopg2.connect( dbnametestdb, userpostgres, passwordpostgres, hostlocalhost ) cur conn.cursor() cities [北京, 上海, 广州, 深圳, 杭州, 成都] for _ in range(200): name fake.name() email fake.email() age random.randint(18, 60) city random.choice(cities) cur.execute( INSERT INTO users (name, email, age, city) VALUES (%s, %s, %s, %s), (name, email, age, city) ) conn.commit()这里有几个细节值得说。Faker.seed(42)和random.seed(42)都要设因为Faker内部有自己的随机源不设的话每次结果不一样。邮箱字段有唯一约束Faker生成的邮箱偶尔会重复数据量大的时候要加去重逻辑。年龄我用了random.randint而不是Faker的random_int效果一样但更直观。5.3 用AI生成商品数据商品数据的特点是业务规则比较多——不同类别的价格区间不同名称要符合类别。这种用Faker生成会很别扭用AI就顺手很多。我用的提示词请生成50条商品记录输出为JSON数组每个对象包含以下字段 - name: 中文商品名要符合category类别 - category: 从[电子产品, 家居用品, 食品, 服装]中选 - price: 浮点数保留两位小数。电子产品100-5000家居用品50-1000食品10-200服装30-800 - stock: 整数0到500 要求四个类别各生成约12到13条价格在各自区间内随机分布。AI返回的JSON直接解析入库import json import requests prompt ...上面的提示词... response requests.post( http://localhost:8000/v1/chat/completions, json{ model: local-model, messages: [{role: user, content: prompt}], temperature: 0.7 } ) content response.json()[choices][0][message][content] # 去掉可能的markdown代码块标记 content content.strip().strip().strip() if content.startswith(json): content content[4:].strip() products json.loads(content) for p in products: cur.execute( INSERT INTO products (name, category, price, stock) VALUES (%s, %s, %s, %s), (p[name], p[category], p[price], p[stock]) ) conn.commit()这里有个坑要提醒AI返回的JSON经常被包在markdown代码块里直接json.loads会报错。我加了一段清理逻辑把反引号和json标记去掉。另外temperature参数建议设低一点0.3到0.7之间太高了结果太随机太低了又不够多样。5.4 订单数据的关联生成订单表是最麻烦的因为它要关联用户和商品还要保证金额计算正确。我的做法是分两步先用SQL随机选用户和商品再用代码计算金额。# 先查出所有用户ID和商品信息 cur.execute(SELECT id FROM users) user_ids [row[0] for row in cur.fetchall()] cur.execute(SELECT id, price FROM products) products cur.fetchall() for _ in range(500): user_id random.choice(user_ids) product_id, price random.choice(products) quantity random.randint(1, 5) amount round(float(price) * quantity, 2) # 下单时间在过去一年内随机 cur.execute( INSERT INTO orders (user_id, product_id, quantity, amount, created_at) VALUES (%s, %s, %s, %s, now() - (random() * interval 365 days)), (user_id, product_id, quantity, amount) ) conn.commit()金额用price * quantity计算保证和商品价格一致。时间用数据库的random()函数生成过去一年内的随机时间。这样生成出来的订单数据外键关联正确金额逻辑自洽时间分布也合理。5.5 数据质量校验数据生成完不算完得验证一下。我一般跑几个校验查询。检查外键完整性SELECT COUNT(*) FROM orders o LEFT JOIN users u ON o.user_id u.id WHERE u.id IS NULL;检查金额一致性SELECT COUNT(*) FROM orders o JOIN products p ON o.product_id p.id WHERE o.amount ! p.price * o.quantity;检查唯一约束SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) 1;这三个查询返回0基本就说明数据质量没问题。如果有非0结果就要回去检查生成逻辑。6. 常见问题与排查技巧实录6.1 生成速度慢怎么办数据量大的时候逐条insert会非常慢。我试过生成10万条记录逐条插入跑了将近20分钟。后来改成批量插入时间降到1分钟以内。批量插入的写法from psycopg2.extras import execute_values data [(name, email, age, city) for ...] execute_values( cur, INSERT INTO users (name, email, age, city) VALUES %s, data, page_size1000 )page_size控制每次提交的条数1000到5000之间比较合适。太小了提交次数多太大了内存占用高。6.2 AI生成的数据格式不对怎么处理AI返回的数据格式出问题是常态常见的有几种JSON被markdown包裹、字段名和预期不一致、数值类型变成了字符串、日期格式不统一。我的处理策略是加一层规范化函数不管AI返回什么都先过一遍这个函数再入库。def normalize_product(p): return { name: str(p.get(name, )).strip(), category: p.get(category, 其他), price: round(float(p.get(price, 0)), 2), stock: int(p.get(stock, 0)) }这样即使AI返回的字段类型有偏差也能被纠正过来。日期字段用dateutil.parser解析比手动写格式兼容性好。6.3 外键关联对不上怎么排查外键对不上通常有两个原因一是生成从表数据时用了不存在的ID二是主表数据被删了但从表没跟着删。排查思路是先查孤儿记录SELECT o.id, o.user_id FROM orders o LEFT JOIN users u ON o.user_id u.id WHERE u.id IS NULL LIMIT 10;如果确实有孤儿记录要么补上主表数据要么删掉从表记录。预防的办法是在生成从表数据前先把主表的ID列表查出来缓存生成时只从这个列表里选。6.4 数据分布不真实怎么调整均匀随机生成的数据在真实场景下往往不真实。比如用户年龄真实分布可能集中在25到35岁而不是18到60岁均匀分布。调整分布可以用random.choices配合权重age_ranges [(18, 25), (25, 35), (35, 45), (45, 60)] weights [0.15, 0.45, 0.25, 0.15] chosen_range random.choices(age_ranges, weightsweights, k1)[0] age random.randint(*chosen_range)这样生成出来的年龄分布就更接近真实情况。金额字段可以用对数正态分布用random.lognormvariate生成。6.5 常见问题速查表问题现象可能原因排查方法解决方式插入报外键约束错误从表引用了不存在的主表ID查孤儿记录先查主表ID列表再生成从表唯一约束冲突生成的值重复查重复值加去重逻辑或改用UUIDAI返回JSON解析失败被markdown包裹或格式错误打印原始返回加清理和规范化函数生成速度慢逐条插入看执行时间改批量插入调page_size数据分布不真实均匀随机画分布图用加权随机或指定分布结果不可复现没设随机种子对比两次结果设Faker.seed和random.seed日期格式不对AI输出格式不统一检查字段值用dateutil统一解析避坑技巧生成数据前先备份一份空表结构数据生成失败时直接truncate重来比一条条删快得多。另外建议把生成脚本的参数数据量、种子、分布权重都抽成配置文件换场景时改配置就行不用改代码。7. 我个人的一些实操体会测试数据生成这件事工具选型其实不是最关键的最关键的是想清楚数据要用来干什么。做单元测试几条数据就够了Faker随便生成一下就行做性能测试要的是数据量和分布真实得用批量生成加分布控制做演示要的是数据好看、业务合理AI生成反而更合适。AI的介入确实降低了一些场景的门槛尤其是那些“描述起来容易、写代码麻烦”的需求。但它不是替代品更像是补充。确定性、可复现、数据安全这三条底线目前还是得靠传统工具和本地流程来守。最后分享一个小技巧如果你经常需要生成类似结构的数据可以把提示词模板化把变化的字段抽成变量。比如建一个prompt_templates目录每个场景一个模板文件用的时候替换变量就行。这样既保留了AI的灵活性又提高了复用率。我用了这个办法之后生成新场景数据的准备时间从十几分钟降到了两三分钟。