测试数据工厂:解决自动化里最脏的问题

做自动化两年,越来越确信:数据问题是自动化稳定性的第一来源,比 UI 变化更频繁、比网络问题更隐蔽。用例挂掉,排查路径经常是"选择器没问题 → 时序没问题 → 数据不对"。这篇讲我们建的"测试数据工厂",把数据从用例里彻底剥离。

一、数据问题的三个典型形态

  1. 用例间污染:A 用例创建了"测试店铺",B 用例也创建,C 用例的全局列表里出现两个,断言数量直接挂;
  2. 前置数据缺失:用例假设"有一个带 3 条消息的会话",但环境里这个会话上周被人手动删了——用例挂了,日志却指向 UI;
  3. 数据规模失真:列表页用例永远只跑 3 条数据,线上 3000 条时的分页、懒加载、性能问题全测不到。

三个形态的共同点:用例自己造数据、清数据,造的方式散落各处,没人知道环境里此刻有哪些数据。

二、数据工厂的核心模型:Profile + Factory + Cleanup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 数据声明(YAML,和用例分离)
# data/profiles/conversation_with_msgs.yaml
name: conversation_with_msgs
desc: 一个带 N 条消息的活跃会话
params:
msg_count: { default: 3, type: int }
with_media: { default: false, type: bool }

build: # 构造步骤(工厂执行)
- create_conversation
- send_messages { count: "${msg_count}", media: "${with_media}" }

teardown: # 清理步骤(对称)
- delete_conversation
1
2
3
4
5
6
# 用例里只声明依赖,不写构造代码
@pytest.mark.data("conversation_with_msgs")
def test_message_list_render(app):
conv = data.current("conversation_with_msgs") # 工厂已建好,直接引用
open_conversation(app, conv.id)
assert message_count(app) == conv.msg_count

设计要点:

  • Profile 是数据契约:用例声明"我要什么数据"(名字 + 参数),不关心怎么造。数据构造逻辑变更(接口改版)只改 Profile,用例零改动;
  • build/teardown 对称:每个构造步骤必须有对应清理,工厂强制校验——不对称的 Profile 不许入库;
  • 数据对象带元数据conv.id / conv.msg_count / conv.created_at 全部结构化,用例断言引用的是数据对象的真实值,永远不写死 "3 条消息"这种字面量。

三、隔离:命名空间 + 租约

多组用例并发跑(分布式执行机场景)时,数据必须互相看不见:

1
2
3
4
5
6
7
8
9
10
11
class DataFactory:
def provision(self, profile, namespace):
"""
namespace = <run_id>/<worker_id>,工厂给该命名空间内
所有实体注入前缀:shop_8f3a21_b0、conv_8f3a21_b0_1 ...
"""
...

def lease(self, namespace):
"""租约:30 分钟无心跳自动回收,防 worker 崩溃后数据泄漏"""
...
  • 命名空间前缀是隔离的基本盘:所有实体名带 run_id,用例之间、批次之间物理隔离,全局列表页也不会互相污染;
  • 租约回收兜底清理失败:teardown 没跑完(崩溃/取消)的命名空间,后台任务按租约过期强制回收——数据泄漏从"积累到出问题"变成"最多存活 30 分钟"
  • 隔离的代价是数据变多,所以工厂同时提供共享池:只读型大数据集(3000 条列表、10 万条用户)按版本预生成、全命名空间共享,只有"会被用例修改"的数据才走隔离。

四、数据规模:Profile 参数化出"压力档"

"数据规模失真"的解法是把规模变成 Profile 参数,CI 分档跑:

1
2
3
# data/profiles/order_list.yaml
params:
order_count: { default: 20 } # 日常档:20 单,用例秒级跑完
1
2
3
4
# CI 配置
smoke: data_overrides = order_list.order_count=5
api: data_overrides = order_list.order_count=50
regression: data_overrides = order_list.order_count=3000 # 发版前才跑大规模

大规模数据只在发版前跑是成本与收益的平衡:日常 CI 用 20 条验证逻辑,发版回归用 3000 条验证分页/懒加载/性能。数据规模第一次成了 CI 配置里的显式参数,而不是"环境里恰好有多少"。

五、落地效果

指标 数据工厂前
用例失败中"数据原因"占比 ~40% <8%
环境数据泄漏清理 人工,每周 1 次 租约自动,零人工
新增用例的数据准备时间 0.5~2 小时(手工造数据) 声明 Profile,分钟级
跨用例污染类 bug 偶发难查 命名空间隔离后归零

六、小结

数据工厂的本质是把"数据"从用例的执行逻辑里提升为一等公民:有契约(Profile)、有生命周期(build/teardown/租约)、有隔离(命名空间)、有规模参数。用例从此只表达"业务断言",数据问题不再污染测试结论——这是所有测试平台里投入产出比最高的基础设施。