关于 test-data-management
面向测试工程师的测试数据管理技能,涵盖数据工厂、固定数据、播种、清理和匿名化。它强调每个测试独立拥有数据,以支持确定性、并行执行和隐私保护。
主要功能
- 01使用工厂生成可覆盖的数据
- 02用固定数据模拟响应与基准文件
- 03创建幂等数据库播种
- 04匿名化生产数据中的个人信息
- 05隔离测试数据并清理状态
- 06生成合成边界与极端数据
适用场景
- 01为每个测试创建独立实体数据
- 02准备 API 模拟响应与参考数据
- 03在非生产环境使用匿名化数据
- 04并行执行测试并清理其数据
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ petrkindlmann/ qa-skills --skill test-data-management安装后在 Claude Code 中输入/test-data-management确认已加载。
附带文件references/factories.mdreferences/seeding-and-synthetic.md
详细介绍
快速导航
| 情况 | 前往 |
|---|---|
| 需要为每个测试生成带有独立覆盖值的新实体数据 | 工厂模式 → references/factories.md |
| 模拟 API 响应或黄金文件 | 固定数据策略 → references/factories.md |
| 在非生产环境中使用生产数据 | 数据匿名化 |
| 为测试数据库 / 参考数据幂等填充数据 | 数据库播种 → references/seeding-and-synthetic.md |
| 在测试后清理数据 / 实现并行隔离 | 清理策略 → references/seeding-and-synthetic.md |
| 生成边缘案例和边界值 | 合成数据 → references/seeding-and-synthetic.md |
调研问题
设计测试数据策略前,先了解当前情况。首先检查 .agents/qa-project-context.md——如果该文件存在,请以其为基础,并跳过其中已有答案的问题。
当前数据实践
- 目前如何创建测试数据?(手动、脚本、复制生产数据、没有)
- 测试是否共享数据,还是每个测试都自行创建数据?
- 如何清理测试数据?(清空表、回滚、手动、从不)
- 是否有种子脚本?这些脚本是否幂等?
隐私与合规
- 产品是否处理个人身份信息?(姓名、电子邮件、地址、电话号码、SSN)
- 是否有 GDPR、HIPAA、PCI-DSS 或其他数据保护要求?
- 是否曾在测试环境中使用生产数据?
规模与复杂度
- 测试数据集有多大?(数十条、数千条、数百万条记录)
- 数据关系有多复杂?(简单的 CRUD、深层嵌套层级、多态关系)
- 是否存在跨服务的数据依赖?(共享数据的微服务)
核心原则
1. 每个测试都拥有自己的数据
依赖预先存在的共享数据会让测试变得脆弱。当测试 A 修改共享数据时,测试 B 就会失败。每个测试都应只创建所需的数据,使用这些数据进行验证,并在测试后自行清理。这样可以支持并行执行,并消除对执行顺序的依赖。
2. 动态数据优先使用工厂,而非固定数据
静态固定数据(JSON/YAML 文件)适用于不会变化的参考数据(国家代码、货币列表)。对于测试需要创建和操作的实体数据(用户、订单、产品),应使用工厂函数生成具有合理默认值的新实例,并允许为每个测试覆盖相应的值。
3. 使用生产数据前先进行匿名化
生产数据库包含最贴近真实的数据,但也包含真实用户信息。未经匿名化,绝不要将生产数据复制到测试环境。将个人身份信息替换为合成数据,同时保留数据分布和关系。
4. 确定性数据有助于实现可复现测试
无论测试何时、何地运行,都应产生相同结果。避免在断言中使用 Math.random()、Date.now() 或自增 ID。使用带种子的随机生成器(faker.seed(n))、固定时间戳、工厂序列;如果断言中必须使用 UUID,则使用带种子的 faker.string.uuid(),使其在不同运行中保持稳定。
5. 减少数据,增强信号
只创建每个测试所需的数据。测试用户搜索时,不需要完整的用户档案,包括账单地址、支付方式和订单历史。过度指定测试数据会模糊测试意图,并增加维护负担。
工厂模式
工厂是生成测试数据的函数,带有合理的默认值,让各个测试只需覆盖其场景中重要的数据。TypeScript 默认使用 Fishery(2.4.0),Ruby 使用 FactoryBot(6.6.0),Python 使用 factory-boy(3.3.3);它们都搭配 faker(v10.4.0),生成逼真的字段值。
完整的 Fishery(含关联和确定性 UUID)、FactoryBot(User + Product 的 out_of_stock/discounted traits)、Factory Boy(class Params + Trait)和 Playwright fixture 实现,请参阅 references/factories.md。所有工厂都遵循以下模式:
- 默认值 + 覆盖项 —
Factory.define生成合理的默认值;测试只需传入所关注字段的覆盖值(userFactory.build({ role: 'admin' }))。 - 为唯一字段设置序列 —
sequence(Fishery)、sequence(:email)(FactoryBot)、factory.Sequence(Factory Boy)——绝不硬编码 ID 或电子邮件地址。 - 用 traits 表示变体 — 为常见状态命名(
:admin、:inactive、out_of_stock、discounted),而不是为每种组合单独创建 fixture 文件。 - 关联 — 一个工厂构建另一个工厂(订单构建其用户),无需手动连接,同时保持引用结构。
何时使用工厂而非 fixture
| 场景 | 工厂 | 静态 fixture |
|---|---|---|
| 测试创建或修改的实体数据 | 是 | 否 |
| 参考数据(国家、货币、配置) | 否 | 是 |
| 每个测试中有许多变体的数据 | 是 | 否——文件数量会激增 |
| 关系复杂的数据 | 是——使用关联 | 否——难以维护 |
| API 响应模拟 | 否 | 是——JSON fixture |
| 快照/黄金文件对比 | 否 | 是 |
判断规则: 如果数据有生命周期(在测试期间创建、修改、删除),就使用工厂。如果数据是只读的参考资料,就使用 fixture 文件。
Fixture 策略
references/factories.md 中介绍了三种 fixture 形式:
- 静态 fixture(JSON/YAML) — 最适用于 API 响应模拟(
page.route+route.fulfill)、配置数据和黄金文件对比。 - 动态 fixture(Playwright) —
test.extend在测试前通过 API 创建数据,并在await use(...)后删除数据。这是标准的单测试设置/清理方式。 - Fixture 组合 — 在单个
test.extend中组合由工厂构建的数据(userFactory.build()、orderFactory.buildList(3)),在一个步骤中完成数据填充和清理。
数据匿名化
当需要使用生产数据进行逼真的测试时,应先对其进行匿名化。
PII 遮蔽规则
| 数据类型 | 匿名化方法 | 示例 |
|---|---|---|
| 电子邮件 | 使用 Faker 生成符合原域名模式的电子邮件 | [email protected] -> [email protected] |
| 全名 | Faker 姓名 | Jane Doe -> Alice Johnson |
| 电话号码 | 使用 Faker 生成电话,保留格式 | +1-555-123-4567 -> +1-555-987-6543 |
| 地址 | 使用 Faker 生成地址,保留国家/地区 | 123 Main St, NYC -> 456 Oak Ave, NYC |
| SSN/国家身份号码 | 测试格式 | 123-45-6789 -> 000-00-0001 |
| 信用卡 | 测试卡号 | 4111-... -> 4242-4242-4242-4242 |
| 出生日期 | 按固定偏移量调整 | 1990-03-15 -> 1987-07-22 |
匿名化流程——使用带种子的 Faker 确保确定性、使用内存查找表、按父记录优先排序,并使用封装事务——见 references/seeding-and-synthetic.md(使用 Faker.js 匿名化、匿名化期间的引用完整性)。匿名化用户的电子邮件时,也必须更新所有引用该电子邮件的位置(订单、评论、审计日志);使用同一个查找表,先处理父记录,再处理子记录,并将所有操作放在一个事务中。
GDPR 合规检查清单
- 任何非生产环境中都不存在真实的 PII
- 匿名化不可逆(不存储可映射回原始数据的查找表)
- 匿名化保留数据分布(年龄范围、地理分布),以便进行真实测试
- 无法通过组合准标识符重新识别匿名化数据
- 数据保留政策适用于测试环境(N 天后自动删除)
- 匿名化流水线自动运行,而非手动执行(避免人为错误)
数据库播种
幂等的播种脚本
播种脚本必须支持安全地重复运行,而不产生重复数据。使用 upsert——INSERT ... ON CONFLICT (natural_key) DO UPDATE SET ...——并以稳定的自然键而非主键作为依据。先 DELETE 再 INSERT 的“重置”操作不具备幂等性:它会破坏外键关系,并重新分配序列 ID。完整的 INSERT ... ON CONFLICT (code) DO UPDATE 国家/货币示例及相关原因,请参见 references/seeding-and-synthetic.md(幂等的播种脚本)。
数据库分支(DB 即服务)
如果你的生产数据库托管在 Neon、Supabase 或 PlanetScale 上,分支功能可以为 PR 提供独立数据库,而不必从头播种——但各服务商在分支是否包含数据方面有所不同:
- Neon Branching — 几秒内即可创建写时复制的 Postgres 分支,并包含数据;非常适合临时预览环境。这是“PR 获得真实数据库副本”最有力的方案。
- Supabase Branching —
supabase branches create pr-123会克隆架构,并可选择从备份中克隆数据;预览环境指向分支 URL。 - PlanetScale Branching — MySQL 分支默认仅包含架构(不含数据),因此仍需为分支播种。注意:PlanetScale 已于 2024 年 4 月移除免费的 Hobby 套餐;MySQL 现价约为每月 39 美元起,Postgres 约为每月 5 美元起。
结合 test-environments 中的预览环境模式使用。
避免使用:Snaplet(托管版) — 已于 2024 年 8 月 31 日停止服务;团队已加入 Supabase。
@snaplet/seed以supabase-community/seed的形式继续维护(由社区维护,最近一次有实质意义的发布为 v0.98.0, 时间为 2024 年 7 月,此后没有功能开发)。对于新项目,优先使用上述数据库分支服务商 以及由工厂生成的播种数据。
按测试或按测试套件准备数据
| 策略 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 按测试设置/清理 | 会修改数据的测试 | 完全隔离,可安全并行 | 速度较慢,需要编写更多设置代码 |
| 按测试套件播种 | 只读参考数据 | 快速、简单 | 测试无法修改数据 |
| 按工作进程播种 | Playwright 并行工作进程 | 兼顾速度与隔离 | 需要工作进程作用域的 fixture |
| 全局播种 | 环境初始化 | 只运行一次,设置基线 | 必须具备幂等性,有共享状态风险 |
有关支持按工作进程播种的工作进程作用域 fixture(使用 { scope: 'worker' } 的 test.extend),请参见 references/factories.md(工作进程作用域播种)。
清理策略
| 策略 | 适用场景 | 速度 |
|---|---|---|
| 事务回滚 | 可直接访问数据库的单元/集成测试 | 最快 |
截断(TRUNCATE ... CASCADE) | 在测试套件之间重置表 | 中等 |
| 基于 API 的清理 | 无法直接访问数据库的 E2E 测试 | 最慢 |
事务回滚无法清理 E2E 测试——应用会自行打开数据库连接,因此测试端的事务无法撤销应用的写入;这类测试应使用基于 API 的清理(按创建顺序的逆序删除)。三种实现均见 references/seeding-and-synthetic.md(清理策略)。
合成数据生成
工厂应能轻松生成边界情况和边界值,无需在每个测试中手动编写。可复用的数组和辅助函数——edgeCaseStrings(空字符串、空白、超长字符串、XSS、SQL 注入、空值/控制字符、RTL 覆盖字符)、edgeCaseDates,以及通过 test.each 驱动测试的 boundaryValues(min, max)——位于 references/seeding-and-synthetic.md(合成数据生成)。
反模式
共享的可变测试数据
多个测试读取和写入相同的数据库行。测试 A 创建用户,测试 B 修改用户,测试 C 断言原始状态并失败。解决方法是让每个测试通过工厂创建自己的数据。
未匿名化的生产数据
将生产数据库复制到预发布环境以进行“逼真测试”。这违反 GDPR,会在安全性较低的环境中造成数据泄露风险,并带来合规责任。使用前务必进行匿名化,或生成符合生产数据分布的合成数据。
非确定性数据
创建测试数据时使用 Math.random() 或 Date.now(),却没有设置种子。测试周一通过,周二却失败,因为随机生成的名称恰好超过了字段长度限制。使用带种子的 Faker 实例和固定时间戳。
没有清理策略
测试创建数据后从不清理。测试数据库不断增长,直到影响性能;或者过期数据导致其他测试误报。每次创建数据都必须有相应的清理操作。
夹具文件泛滥
为每种测试变体都创建单独的 JSON 夹具文件。不要使用 user-admin.json、user-inactive.json、user-admin-inactive.json,而应使用带特征的工厂。夹具应仅用于静态参考数据和 API 响应模拟。
测试数据指定过多
测试只关注 role,却创建包含 30 个字段的完整用户对象。这会模糊测试意图,并让测试变得脆弱。具有合理默认值的工厂可以解决这个问题:只覆盖测试关注的内容。
硬编码 ID
在测试中使用 userId: '1'。这会让测试依赖数据库状态,并在并行运行时(ID 冲突)或针对已有数据的数据库运行时导致失败。使用工厂序列或带种子的 UUID(参见核心原则 4)。
验证
证明数据层具有确定性、相互隔离且不含个人身份信息(PII),并从最小的检查开始:
- 种子数据可幂等执行——连续运行两次种子脚本,并比较行数:
psql -c "SELECT count(*) FROM countries" && <seed> && psql -c "SELECT count(*) FROM countries"两次返回的数字相同,且退出码为 0。数量不断增长表示缺少ON CONFLICT。 - 没有共享的可变状态——在并行执行和随机顺序下运行测试套件:
npx playwright test --workers=4(或pytest -n auto -p randomly)应全部通过。只有在此情况下才出现的失败,说明存在顺序依赖或共享数据依赖。 - 保持确定性——对同一个数据生成测试运行两次;设置
faker.seed(n)后,生成的名称/ID/UUID 在两次运行中应一致。如果结果发生变化,说明有未设置种子的 Faker 调用,或混入了Date.now()/crypto.randomUUID()。 - 没有真实 PII——
grep -rE '@(gmail|outlook|yahoo)\.com|[0-9]{3}-[0-9]{2}-[0-9]{4}' tests/ fixtures/不应返回任何内容(真实形式的邮箱和 SSN)。任何匹配结果都表明存在匿名化缺口。 - 清理后恢复到基线——在测试套件运行前后分别记录行数:测试 DB 应恢复到基线,不留下孤立记录。
完成标准
- 套件创建的每种实体类型都有工厂或 fixture(测试中不为共享实体内联临时编写对象字面量——在测试目录中搜索手工构建的 fixture,并确认没有遗漏)。
- 每个测试的数据彼此隔离——套件在启用并行(
--workers=N/pytest -n auto)以及随机顺序(--shuffle/-p randomly)下均能通过,证明不存在共享可变状态或顺序依赖。 - 种子脚本具有幂等性——连续运行两次种子脚本会生成相同的行数并以状态码 0 退出;CI 作业会自动运行这些脚本。
- 测试 fixture 中不使用真实个人身份信息(PII)——所有敏感数据均已匿名化或为合成数据(搜索生产环境域名 / 看起来真实的 SSN 时没有任何匹配结果)。
- 已验证数据清理——测试套件运行后,测试数据库中的行数会恢复到基线(多次运行不会累积孤立记录)。
参考文件(位于 references/ 中)
- factories.md — 完整介绍 Fishery(关联、确定性 UUID)、FactoryBot(User + Product traits)、Factory Boy(
Params/Trait)、静态 / 动态 / 组合式 Playwright fixture,以及 worker 作用域的种子 fixture。 - seeding-and-synthetic.md — 幂等的
ON CONFLICT种子脚本、Faker.js 匿名化 + 参照完整性流水线、清理策略(回滚 / 截断 / API),以及合成边界情况和边界值生成器。
相关技能
- unit-testing -- 单元测试是使用工厂生成数据的主要场景;此技能提供数据层。
- api-testing -- API 测试同时使用工厂(生成请求体)和 fixture(模拟响应)。
- playwright-automation -- E2E 测试需要在浏览器交互前通过 API 或 fixture 填充测试数据。
- test-reliability -- 确定性测试数据可消除测试不稳定的一个主要来源。
- test-environments -- 负责预览环境的环境配置和数据库分支策略(Neon、Supabase、PlanetScale);此技能负责填充这些环境的数据。
- database-testing -- 专门针对数据库层的迁移测试、数据完整性断言和 Testcontainers——测试数据库请使用该技能,填充数据库则使用此技能。
- ci-cd-integration -- 数据库种子和清理必须集成到 CI 流水线阶段中。