上周四晚上 7 点 47 分,办公室只剩我一个人。
电脑屏幕右下角弹出飞书消息:
业务负责人:"@测试 周一早上必须上线,老板周一要看演示,没问题吧?"
我看了看时间,又看了看测试环境——空空如也。
我:"测试数据还没铺,回归也没跑。现在 7 点半,我估计要搞到11点..."
业务负责人:"不是就测个功能吗?要这么久?"
我:"每次回归都要手动铺 20 条商品数据,铺完还要一条条清理,不然下次测试数据就脏了。这已经是最快速度了..."
消息框显示"正在输入",很久,最后只发来一句:
业务负责人:"那你也得想办法,周一必须上线。"
我深吸一口气,继续手动创建商品:
- 第 1 条:名称、价格、库存、分类... 3 分钟
- 第 2 条:名称、价格、库存、分类... 3 分钟
- 第 3 条:名称、价格、库存、分类... 3 分钟
...
第 20 条铺完,已经晚上 8 点 15 分。
我开始跑回归测试,50 个用例,预计 40 分钟。
晚上8 点 55 分,测试跑完,48 个通过,2 个失败。
我打开日志,发现是数据问题——有两条数据的分类 ID 被研发改成了无效值。
那一刻,我真的想哭。
又要重新铺数据。
晚上9点 40 分,我终于关上办公室的门。
打车回家,路上花了 87 块钱。
第二天早上 10 点
业务负责人路过,说:"昨天测得怎么样?"
我说:"测完了,但铺数据花了很长时间。"
业务负责人:"铺个数据要这么费劲?"
我没说话。
而这样的加班,每个月都在上演。
这样的时间浪费,你还要忍受多久?
为什么手搓数据会拖垮回归测试?
你是不是也经历过这些:
❌ 测试环境好好的,一回归就报错,原来是数据被踩了
❌ 每次回归前,都要花半天时间铺数据,测完还要清理
❌ 写了脚本但没人用,因为"环境不一样,跑不起来"
❌ 老板问"测了没",你说"在等数据",空气突然安静
如果中了一条,这篇文章就是为你写的。
我算了一笔账:
手搓数据的真实成本
| 操作 | 单次耗时 | 每周次数 | 每月耗时 |
|---|---|---|---|
| 手动铺数(20 条) | 30 分钟 | 5 次 | 10 小时 |
| 手动清理数据 | 15 分钟 | 5 次 | 5 小时 |
| 处理数据污染 | 60 分钟 | 2 次 | 8 小时 |
| 修复脚本适配 | 90 分钟 | 2 次 | 12 小时 |
| 合计 | - | - | 35 小时/月 |
这还只是一个模块!
如果有 10 个模块需要回归测试,每月就是 350 小时,相当于 1.75 个人力 全月都在铺数据!
你的收益计算器
如果你的团队:
- 有 5 个测试人员
- 每人每月手搓数据 35 小时
- 时薪 100 元
实施前:5 × 35 × 100 =17,500 元/月
实施后:5 × 1 × 100 =500 元/月
每月节省:17,000 元
每年节省:204,000 元 这还没算减少的扯皮、提升的质量、避免的线上故障。
更重要的是:
你的团队可以把节省下来的 170 小时/月,用来:
- 做更有价值的探索性测试
- 学习自动化和性能测试
- 早点下班,陪陪家人
而不是把生命浪费在重复的体力劳动上。
测试数据工厂的设计原则
在动手写代码前,我定了 3 条不可违背的原则:
原则 1:幂等性
问题:脚本执行一半失败了,再执行一次会怎样?
错误做法:
PYTHON复制
❌ 直接插入,不管是否已存在
def create_product(name, price, stock):
db.insert("products", name, price, stock)
正确做法:
python
✅ 先查后删再插,保证幂等
def create_product(name, price, stock):
existing = db.query("SELECT
FROM products WHERE name = ?", name)
if existing:
db.delete("products", existing.id)
db.insert("products", name, price, stock)
原则 2:可追溯
问题:环境里有 100 条 E2E_AUTO_ 开头的商品,哪些是今天测试的?哪些是上周遗留的?
错误做法:
PYTHON复制
❌ 所有数据都用同一个前缀
PREFIX = "E2E_AUTO_"
create_product(f"{PREFIX}商品 1", ...)
正确做法:
python
✅ 加上时间戳批次 ID
run_id = datetime.now().strftime("%Y%m%d_%H%M%S")
PREFIX = f"E2E_AUTO_{run_id}_"
create_product(f"{PREFIX}商品 1", ...)
原则 3:自动清理
问题:测试跑完了,数据谁清理?
错误做法:
PYTHON复制
❌ 测试脚本里顺手清理
def test_product_creation():
create_product(...)
assert ...
delete_product(...) # 如果测试失败,这行不会执行!
正确做法:
python
✅ 独立的清理脚本,CI 失败也要执行
seed_products.py
铺数
teardown_products.py
清理(无论测试成功失败都执行)
完整代码实战
环境准备
我准备了一个简易电商后台(Flask 实现),包含商品管理功能:
电商后台首页
图 1:电商后台首页
- 铺数前只有 3 个商品
BASH复制
安装依赖
pip3 install flask requests
启动服务
python3 ecommerce_app.py
访问地址
http://localhost:5000
测试账号
admin / 123456
API 端点:
POST /login - 登录GET /products - 获取商品列表POST /products - 创建商品DELETE /products/ - 删除商品
铺数脚本(seed_products.py)
完整代码:
PYTHON复制
#!/usr/bin/env python3
"""
测试数据工厂
商品铺数脚本
原则:幂等、可追溯、自动清理
"""
import requests
import json
import time
from datetime import datetime
from pathlib import Path
部分代码示例,粘出来实在太长了,需要的话私信获取;
关键点解析:
1. 带重试的创建逻辑:网络抖动、并发冲突都可能导致失败,自动重试 3 次
2. 幂等性保证:如果商品已存在(409),先删除再创建
3. 预期失败处理:区分"意外失败"和"预期失败",避免误报
4. 结果持久化:保存 JSON 报告,供后续分析和飞书推送使用
清理脚本(teardown_products.py)
完整代码:
PYTHON复制
#!/usr/bin/env python3
"""
测试数据工厂
清理脚本
原则:谁创建谁清理、带重试、带日志
"""
import requests
import json
import time
import argparse
from datetime import datetime
from pathlib import Path
部分代码示例,粘出来实在太长了,需要的话私信获取;
关键点解析:
1. 按前缀过滤:只清理 E2E_AUTO_ 开头的数据,避免误删真实业务数据
2. 按批次清理:支持 --run-id 参数,只清理特定批次的数据
3. 带重试的删除:删除失败自动重试 3 次
4. 详细日志:每一步都有日志输出,方便排查问题
实战演示
执行铺数
📸 截图说明:
- 图 01:电商后台首页(http://localhost:5000)
- 图 02:铺数前的商品列表(3 个商品)
- 图 05:铺数脚本执行输出
- 图 04:seed_result.json 内容
BASH复制
cd /home/liu/hermes-demo
python3 seed_products.py
输出(实际执行结果):
🚀 开始铺数,批次 ID: 20260411_163612
============================================================
✅ 登录成功
[1/10] 创建:E2E_AUTO_正常商品_1 (价格:99.0, 库存:100)
✅ 创建成功 (ID:12)
[2/10] 创建:E2E_AUTO_正常商品_2 (价格:199.5, 库存:50)
✅ 创建成功 (ID:13)
[3/10] 创建:E2E_AUTO_正常商品_3 (价格:299.99, 库存:75)
✅ 创建成功 (ID:14)
[4/10] 创建:E2E_AUTO_边界_价格 0 元 (价格:0.0, 库存:10)
✅ 创建成功 (ID:15)
[5/10] 创建:E2E_AUTO_边界_库存 0 (价格:50.0, 库存:0)
✅ 创建成功 (ID:16)
[6/10] 创建:E2E_AUTO_边界_最大价格 (价格:99999.99, 库存:1)
✅ 创建成功 (ID:17)
[7/10] 创建:E2E_AUTO_小数_价格 2 位 (价格:99.99, 库存:10)
✅ 创建成功 (ID:18)
[8/10] 创建:E2E_AUTO_小数_价格 3 位 (价格:99.999, 库存:10)
✅ 创建成功 (ID:19)
[9/10] 创建:E2E_AUTO_异常_负价格 (价格:-10.0, 库存:10)
⚠️ 预期失败:创建失败:{"error":"价格必须大于等于 0"}
[10/10] 创建:E2E_AUTO_异常_负库存 (价格:50.0, 库存:-5)
⚠️ 预期失败:创建失败:{"error":"库存必须大于等于 0"}
============================================================
📊 铺数完成
总数:10
成功:8
失败:0
预期失败:2
成功率:80.0%
📁 结果已保存:/home/liu/hermes-reports/seed_result.json
💡 技术细节:
- 铺数前商品列表:3 个(iPhone 15、MacBook Pro、AirPods Pro)
- 铺数后商品列表:11 个(原有 3 个 + 新增 8 个 E2E_AUTO_商品)
- 成功率 80%:8 个成功 + 2 个预期失败(负价格、负库存被业务规则拒绝)
- 批次 ID:
20260411_163612(时间戳格式,保证可追溯)
============================================================
✅ 登录成功
[1/10] 创建:E2E_AUTO_正常商品_1 (价格:99.0, 库存:100)
✅ 创建成功 (ID:4)
[2/10] 创建:E2E_AUTO_正常商品_2 (价格:199.5, 库存:50)
✅ 创建成功 (ID:5)
...
[9/10] 创建:E2E_AUTO_异常_负价格 (价格:-10.0, 库存:10)
⚠️ 预期失败:创建失败:{"error":"价格必须大于等于 0"}
[10/10] 创建:E2E_AUTO_异常_负库存 (价格:50.0, 库存:-5)
⚠️ 预期失败:创建失败:{"error":"库存必须大于等于 0"}
============================================================
📊 铺数完成
总数:10
成功:8
失败:0
预期失败:2
成功率:80.0%
📁 结果已保存:seed_result.json
执行清理
BASH复制
python3 teardown_products.py --prefix "E2E_AUTO_"
输出(实际执行结果):
✅ 登录成功
🧹 开始清理数据
前缀:E2E_AUTO_
📋 找到 8 条匹配数据
[1/8] 删除:E2E_AUTO_正常商品_1 (ID:12)
✅ 删除成功
[2/8] 删除:E2E_AUTO_正常商品_2 (ID:13)
✅ 删除成功
[3/8] 删除:E2E_AUTO_正常商品_3 (ID:14)
✅ 删除成功
[4/8] 删除:E2E_AUTO_边界_价格 0 元 (ID:15)
✅ 删除成功
[5/8] 删除:E2E_AUTO_边界_库存 0 (ID:16)
✅ 删除成功
[6/8] 删除:E2E_AUTO_边界_最大价格 (ID:17)
✅ 删除成功
[7/8] 删除:E2E_AUTO_小数_价格 2 位 (ID:18)
✅ 删除成功
[8/8] 删除:E2E_AUTO_小数_价格 3 位 (ID:19)
✅ 删除成功
============================================================
📊 清理完成
找到:8
成功:8
失败:0
成功率:100.0%
📁 报告已保存:cleanup_result.json
💡 技术细节:
- 清理前商品列表:11 个(3 个原有 + 8 个 E2E_AUTO_)
- 清理后商品列表:3 个(仅保留原有商品)
- 成功率 100%:8 个 E2E_AUTO_商品全部删除
- 安全机制:只删除 E2E_AUTO_前缀的数据,避免误删真实业务数据
复杂场景处理
场景 1:有外键依赖怎么铺数
问题:创建订单需要先有用户和商品,怎么保证顺序?
解决方案:
PYTHON复制
def seed_order_data():
"""铺订单测试数据"""
# 1. 先铺用户
user_id = create_user("E2E_AUTO_测试用户")
# 2. 再铺商品
product_ids = []
for i in range(3):
pid = create_product(f"E2E_AUTO_商品_{i}")
product_ids.append(pid)
# 3. 最后铺订单
for pid in product_ids:
create_order(user_id, pid)
# 4. 清理时反向删除
# 订单 → 商品 → 用户
清理顺序:与创建顺序相反,先删除子记录,再删除父记录。
场景 2:有状态机怎么覆盖
问题:订单有状态流转(待支付→已支付→发货→完成),怎么测试?
解决方案:
PYTHON复制
def seed_order_states():
"""铺订单状态测试数据"""
states = [
("E2E_AUTO_待支付订单", "pending"),
("E2E_AUTO_已支付订单", "paid"),
("E2E_AUTO_已发货订单", "shipped"),
("E2E_AUTO_已完成订单", "completed"),
("E2E_AUTO_已取消订单", "cancelled"),
]
for name, state in states:
order_id = create_order(...)
transition_order(order_id, state) # 流转到指定状态
场景 3:有分页/限流怎么处理
问题:接口有分页(每页 20 条)和限流(每秒 10 次),怎么铺大量数据?
解决方案:
PYTHON复制
def seed_bulk_products(count=100):
"""批量铺数(带分页和限流处理)"""
session = get_session()
for i in range(count):
name = f"E2E_AUTO_商品_{i}"
create_product(session, name, 99.00, 100)
# 限流处理:每 10 次请求暂停 1 秒
if (i + 1) % 10 == 0:
time.sleep(1)
效果对比
实施前(手搓数据)
| 指标 | 数值 |
|---|---|
| 单次回归铺数时间 | 30 分钟 |
| 单次回归清理时间 | 15 分钟 |
| 每月回归次数 | 20 次 |
| 每月耗时 | 15 小时 |
| 数据污染次数 | 5 次/月 |
| 处理污染耗时 | 10 小时/月 |
| 总计 | 25 小时/月 |
实施后(数据工厂)
| 指标 | 数值 |
|---|---|
| 单次回归铺数时间 | 2 分钟(自动) |
| 单次回归清理时间 | 1 分钟(自动) |
| 每月回归次数 | 20 次 |
| 每月耗时 | 1 小时(仅监控) |
| 数据污染次数 | 0 次/月 |
| 处理污染耗时 | 0 小时/月 |
| 总计 | 1 小时/月 |
效率提升
- 时间节省:25 小时 → 1 小时,效率提升 25 倍
- 人力释放:每月节省 24 小时,相当于 3 个工作日
- 质量提升:数据污染从 5 次/月降为 0
常见踩坑点
坑 1:脚本执行一半失败,数据铺了一半
现象:铺数脚本执行到第 5 条时报错,前 4 条已经创建,再执行一次会重复。
解决:实现幂等性,创建前先检查是否存在,存在则先删除。
坑 2:清理脚本漏删数据
现象:清理脚本只删除了部分数据,遗留的数据影响下次测试。
解决:
- 清理前先查询,输出找到的数据列表
- 清理后再次查询,确认无残留
- 添加清理报告,记录成功/失败数量
坑 3:多环境配置不同
现象:测试环境 URL 是http://test:5000,预发环境是http://staging:5000。
解决:使用配置文件或环境变量:
PYTHON复制
import os
BASE_URL = os.getenv("TEST_BASE_URL", "http://localhost:5000")
给你的行动清单
25 分钟:
- 复制 ecommerce_app.py(5 分钟)
- 复制 seed_products.py(5 分钟)
- 复制 teardown_products.py(5 分钟)
- 启动服务,执行铺数(5 分钟)
- 执行清理,确认成功(5 分钟)
2 小时: - 根据自己业务修改字段(30 分钟)
- 集成到现有测试框架(30 分钟)
- 配置 CI/CD 自动执行(30 分钟)
- 写文档,教会团队成员(30 分钟)
1 天: - 覆盖所有核心模块
- 建立测数工厂规范
- 培训团队全员使用
1 周后:
- 回归测试时间:30 分钟 → 5 分钟
- 数据污染问题:5 次/周 → 0 次
- 业务扯皮次数:5 次/周 → 1 次
这就是工程化的力量。
AI 时代,测试人员的核心竞争力
AI 时代,测试人员的核心竞争力是什么?
不是手工测试,不是用例设计,不是 bug 追踪。
是自动化能力,是工程化思维,是用工具放大自己的价值。
当你的同事还在手搓数据时,你已经用脚本一键铺数;
当你的同事还在手动发报告时,你已经用 Hermes 自动推送;
当你的同事还在被业务追着问"测了没"时,你的报告已经准时出现在飞书群里。
这就是差距。
而差距,是从选择开始的。
下一篇预告
Hermes 实战⑤-下:《回归报告没人看?我用 Hermes 实现"自动推送 + 失败@责任人"》
内容:
- 飞书 webhook 完整配置流程
- 测试摘要契约设计
- 定时报告自动推送
- 失败用例@责任人
- 周报/日报差异化文案
📢 互动话题
你在测试数据上踩过哪些坑?
A. 环境互踩,数据污染
B. 手动铺数,耗时耗力
C. 脚本难维护,总报错
D. 报告没人看,白忙活
评论区告诉我,也可以私信我送《Hermes 测试数据工厂》完整源码(含 Flask 电商后台 + 铺数脚本 + 清理脚本 + 飞书推送)。
最后说句心里话:
测试数据工厂不是"银弹",但它能让你从重复劳动中解放出来,把时间花在更有价值的事情上——比如设计更好的测试用例、发现更深层的 bug。
别再用战术上的勤奋,掩盖战略上的懒惰了。
你的时间,应该花在更有价值的地方。
比如,早点下班,陪陪家人。
作者:测试员周周:testzhouzhou,14 年测试经验,专注 AI+ 测试实战
公众号:测试员周周