测试数据管理
测试数据管理是为测试建立需要的业务状态,并控制这些数据在执行、并行和结束之后的影响。一条用例不仅需要“有一个用户”,还要知道这个用户拥有什么权限、关联哪些记录、能否被其他用例修改,以及失败后怎样清理。
可靠的测试数据应该让失败可以重建,让用例不依赖偶然残留。这里讨论测试自身的数据生命周期;产品是否正确隔离不同用户或租户的数据,属于接口权限验证的检查目标。
1. 面试核心
先记住这几条,再读正文- 造数先定义业务约束和依赖,再选择 API、数据库或工厂方式。造出符合字段类型的数据,不代表已经建立了有效业务状态。卡 · 综合题
- 并行测试需要独立命名和访问范围,运行标识要同时约束创建、查询和清理,不能只在名称后面加随机字符串。卡 · 并行隔离与运行标识
- 事务回滚只作用于对应事务。测试客户端的回滚不能撤销服务端另一连接已经提交的数据,更不能自动撤销消息和文件。卡 · 事务边界与外部副作用
- 数据集应覆盖规则、边界与关键分布,随机种子帮助重放生成过程,却不能替代必要的特殊值和业务约束。卡 · 代表性、敏感信息与清理
- 清理需要考虑依赖顺序和失败,留下可追溯的资源清单及补偿机制,避免删除其他任务的数据。卡 · 代表性、敏感信息与清理
2. 数据构造与依赖
造数工具的输入应该表达测试需要的状态。例如“一个有余额的普通用户和一张待支付订单”,比“往三张表各插十行”更接近用例目的。
可以按业务依赖准备:先创建用户,再创建关联订单,最后校验订单归属和状态。工具输出已创建资源的标识及运行标签,供用例查询和后续清理。
| 构造方式 | 适用场景 | 需要检查的边界 |
|---|---|---|
| 通过业务 API 创建 | 需要真实规则、事件与关联数据 | 准备耗时、外部依赖及部分成功 |
| 直接写数据库 | 明确的数据层测试、较大批量数据 | 是否绕过默认值、校验、缓存与事件 |
| 工厂函数生成对象 | 单元或组件内的局部规则 | 不能据此证明真实数据库约束正确 |
| 恢复基线快照 | 复杂且需要一致起点的环境 | 恢复成本、版本兼容与并行占用 |
直接插入一行 status=PAID,可能没有支付记录、库存变更和事件;后续测试失败不一定是被测功能错误,也可能是准备的数据从未构成合法状态。若故意构造不一致数据来测试防护,需要明确标为该用例的异常条件。
工具不必一开始就支持任意表、任意依赖和任意脚本。先实现真实用例需要的状态,给出输入校验、资源记录、清理及失败证据,再根据已知需求扩展。
3. 并行隔离与运行标识
运行标识标记一轮测试创建的数据。通常还需要工作进程标识和用例标识,以区分同一轮中并行执行的不同任务。
选择隔离粒度时,要看数据如何被访问:
| 粒度 | 可以隔离什么 | 仍需注意 |
|---|---|---|
| 独立资源 ID/业务命名空间 | 不同测试的记录 | 查询和清理必须带相应范围 |
| 独立账号或租户 | 账号下的大部分状态 | 全局唯一约束和公共资源仍可能冲突 |
| 独立 schema/数据库 | 数据库级状态 | 缓存、消息和文件未必随之隔离 |
| 独立环境 | 更多运行资源 | 建立成本、外部共享服务与配额 |
下面用内存 SQLite 演示定向清理。它只验证运行标识的使用,不代表 MySQL 或跨服务实现:
import sqlite3
from contextlib import closing
with closing(sqlite3.connect(":memory:")) as conn:
conn.execute("CREATE TABLE notes (run_id TEXT, note_id TEXT PRIMARY KEY)")
conn.executemany(
"INSERT INTO notes VALUES (?, ?)",
[("run-a", "a-1"), ("run-a", "a-2"), ("run-b", "b-1")],
)
conn.execute("DELETE FROM notes WHERE run_id = ?", ("run-a",))
remaining = conn.execute("SELECT run_id, note_id FROM notes").fetchall()
assert remaining == [("run-b", "b-1")]
print(remaining)
输出只剩 run-b 的记录。若清理写成无条件删除,另一轮测试的数据也会消失;即使每条记录的 ID 都随机生成,仍然无法阻止这种污染。
同理,断言不能无范围查询“最近一条订单”。其他测试可能刚好创建了新记录,导致通过或失败都取决于执行时机。查询应使用本用例保存的业务 ID,而不是依赖全局排序碰巧找到它。
4. 事务边界与外部副作用
事务回滚适合撤销同一事务中尚未提交的数据库修改,但 HTTP 客户端通常不拥有服务端事务。接口处理在另一连接中提交后,测试侧的本地回滚不会传播过去。
下面用两个 SQLite 连接演示连接边界:
import sqlite3
from contextlib import closing
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as directory:
path = Path(directory) / "data.db"
with closing(sqlite3.connect(path)) as setup:
setup.execute("CREATE TABLE notes (id TEXT PRIMARY KEY)")
setup.commit()
with closing(sqlite3.connect(path)) as client, closing(sqlite3.connect(path)) as server:
client.execute("BEGIN")
server.execute("INSERT INTO notes VALUES (?)", ("note-1",))
server.commit()
client.rollback()
count = client.execute("SELECT COUNT(*) FROM notes").fetchone()[0]
assert count == 1
print(count)
输出 1。client 没有撤销 server 已提交的写入;示例没有建立读快照或模拟 MySQL 的锁,只用于说明事务所属连接不同。
真实清理还要考虑事务之外的副作用,例如对象存储中的文件、已投递消息、缓存条目和外部系统记录。可以通过隔离命名空间、业务删除接口和测试专用的补偿任务清理,并分别确认结果。
即使在同一个 MySQL 连接中,也不能把所有语句都当成可回滚的普通 DML。MySQL 8.0 的某些 DDL 会触发隐式提交,非事务表也有不同边界;测试方案必须匹配实际存储和语句语义。
5. 代表性、敏感信息与清理
随机生成数据适合扩大取样,但关键边界应固定保留。标题长度刚好达到上限、空值、重复业务键等输入,不应该依赖随机生成器偶然命中。
固定随机种子可以帮助重放生成过程,还应保存实际生成的数据或生成器版本。随机数算法、生成顺序或依赖版本改变后,同一个种子未必得到之前的业务数据。
性能测试还要考虑数据规模、冷热分布和关联结构。十万条完全相同的小记录,不能自动代表十万条大小不同、访问分布不均的真实业务记录。数据设计应对应被检验的查询和容量假设。
需要生产特征时,优先用合成数据保留必要分布;使用经过授权的数据样本时,对身份字段作一致替换,并检查关联关系没有被破坏。简单删除姓名,仍可能留下可识别的账号、文本或组合信息,处理范围要符合实际数据使用要求。
清理可以按资源依赖逆序执行,或调用明确支持级联的业务接口。进程强制退出后,fixture 可能根本没有机会执行,因此还要保留运行标识、资源清单和超期清理入口;补偿清理不能仅按“创建时间较早”删除,必须确认归属与是否仍有活跃任务。
6 · 面试题卡片
本页题目与参考回答
6.1 怎样设计一个服务于实际用例的测试造数工具?
题型:综合题
考察重点:业务状态、依赖、资源标识和失败处理。
相关内容:数据构造与依赖、并行隔离与运行标识、代表性、敏感信息与清理。
参考回答
以用例需要的业务状态为输入,按依赖构造资源,并输出可追溯标识。 明确选择业务 API、数据库或对象工厂,校验准备结果,避免只插字段却缺少关联状态。
每次运行带独立标签,记录部分成功资源,支持定向清理。先实现已知场景及错误处理,数据边界、分布和敏感信息处理也应由测试目标决定。
6.2 为记录生成随机 ID 后,测试就能安全并行了吗?
考察重点:创建、查询和清理的共同作用域。
相关内容:并行隔离与运行标识。
参考回答
还不够,查询、断言和清理也必须限定到本用例的数据范围。 随机 ID 只能降低创建冲突,不能阻止无条件删除或“查询最新一条”读到其他用例的数据。
还要检查共享账号、余额、配额、缓存和外部服务。必要时扩大隔离粒度,但明确成本与仍然共享的部分。
6.3 测试函数结束时回滚事务,能清理 HTTP 接口创建的所有数据吗?
考察重点:事务归属、提交与外部副作用。
相关内容:事务边界与外部副作用。
参考回答
通常不能,测试侧事务不能撤销服务端另一连接已经提交的数据。 文件、消息和缓存也不会因为数据库回滚自动消失。
应明确每种资源的生命周期,使用独立命名空间、业务清理或补偿任务。数据库内部也要核对存储引擎和隐式提交等边界,不能把所有操作都视为可回滚。
6.4 固定随机种子,是否足以得到可重复且有代表性的测试数据?
考察重点:重放条件与覆盖、分布的区别。
相关内容:代表性、敏感信息与清理。
参考回答
固定种子帮助重放生成过程,但不保证覆盖关键边界或符合真实业务分布。 还要固定生成器条件,必要时保存实际数据,显式保留特殊值和已知故障输入。
数据量、字段大小、关联结构和访问冷热应匹配测试目标。重复生成大量相似记录,不等于建立了有代表性的负载。
6.5 测试进程崩溃或清理失败后,怎样处理残留数据?
考察重点:资源归属、依赖顺序与补偿清理。
相关内容:代表性、敏感信息与清理。
参考回答
预先记录运行标识和资源清单,失败后按明确归属补偿清理,并报告未清理项。 清理要考虑资源依赖顺序和跨服务结果。
定时回收前确认对应任务已结束,不能只按年龄或通用名称删除。正常 fixture 收尾覆盖不了进程被强制终止的情况,因此需要独立恢复入口。
7. 参考资料
- pytest 8.4:fixture 清理与安全准备
- MySQL 8.0:START TRANSACTION、COMMIT 与 ROLLBACK
- MySQL 8.0:引发隐式提交的语句
- Python 3.12:sqlite3
来源:Coding 101 原文 · 免费阅读,引用请保留出处。内容使用说明