自动化工具 测试数据怎么造才不撞车:环境变量与系统变量的一次实践

w471176877 · 2026年10月01日 · 29 次阅读

一、先说那个所有人都遇到过的报错

「该手机号已注册」「设备名称已存在」「订单号重复」—— 写 E2E 的人对这类报错都很熟。它们通常不是因为脚本写错了,而是因为测试数据是写死的:

{ "action": "fill", "locator": { "strategy": "placeholder", "value": "手机号" }, "value": "13800138000" }

第一次跑没问题,第二次跑就撞车。跟着一起来的还有另外两个问题:把脚本从测试环境切到预发,一堆 http://localhost:8080 得手改;账号密码躺在脚本 JSON 里,谁都能看到。

这三个问题看着都像「测试数据问题」,其实需要两种不同的东西:

  • 要共享、要能改的值(域名、账号密码)→ 环境变量;
  • 每次运行都该不一样的值(手机号、身份证、唯一名称)→ 系统变量;
  • 还有一个隐含要求:同一个脚本里多处引用同一个变量时,必须指向同一个值。

下面是我们这边落下来的规则和几个踩过的坑。

二、环境变量:挂在项目上,引用写 {{名字}}

环境变量挂在项目上(一个项目对应一个被测系统),入口在项目列表的行操作里:

项目列表:行操作里的 🔑 是环境变量入口

环境变量管理:键值对 + 引用说明

弹窗里那行提示就是全部契约:

脚本中用 {{变量名}} 引用,运行脚本时替换为对应值。变量名仅允许字母/中文/数字/下划线,且不以数字开头。

几个实现上的选择,都是被实际问题逼出来的:

  • 变量名支持中文({{密码}}、{{测试账号}} 都合法)。中文业务系统里,这比硬写成拼音好维护得多。
  • 值默认密文显示,可点眼睛临时查看 —— 因为这个位置最常放的就是账号密码。
  • 保存是整体替换:界面里增删改完一次性提交,后端按变量名去重(后写覆盖)并丢掉非法名。少了一堆「新增单条/删除单条」的中间状态。
  • 命名错误在输入框当场标红,不用等服务端返回。

替换发生在哪几处是固定的:instruction、url、value、locator.value、locator.name、locator.scope.value、locator.scope.name、assertion.expected、assertion.jsonPath。

最后两个最容易被忽略:断言里也该用变量。比如「新建设备后,详情页标题等于刚填的设备名」,就两处都写 {{设备名}},而不是把值抄一遍 —— 抄一遍,就等着它和随机数据对不上那天。

没定义的变量不会被静默吃掉:保留 {{xxx}} 字面量,同时在运行日志里告警:

[警告] 未定义的环境变量:测试账号、密码

这一点我们坚持了很久:把未定义变量替换成空字符串,会让脚本带着空值一路跑下去,然后在某个完全无关的步骤上报错,排查成本高得多。

用例详情:步骤表格里的 {{变量}} 占位符

三、系统变量:六种「每次都不一样」的数据

另一类是内置的、不需要定义的系统变量,写法一致:

变量 生成内容 例子
{{systemTime}} 当前时间戳(13 位毫秒) test_{{systemTime}} → 唯一账号
{{randomNumber}} 随机数字串,默认 6 位,可 {{randomNumber:8}} SN-{{randomNumber:8}}
{{randomChinese}} 随机常用汉字,默认 2 个,可 {{randomChinese:4}} user_{{randomChinese}}
{{randomPhone}} 随机手机号(1[3-9] 开头 11 位) 直接填手机号字段
{{randomEmail}} 随机邮箱 直接填邮箱字段
{{randomIdCard}} 随机 18 位身份证号(含校验位) 直接填证件号字段

「随机」这两个字在测试里是要讲质量的,几个细节:

  • 身份证号是能过校验的:地区码、生日(1950–2005、日取 1–28 绕开大小月)、顺序码,再按 GB 11643 的权重表算校验位(取模 11)。不是随便凑 18 个数字 —— 否则第一步就被表单校验拦下。
  • 随机汉字从高频字池里取,出来的是能读的中文(人名、标题可以直接用)。
  • 随机邮箱用常见域名(qq.com / 163.com / 126.com / gmail.com / outlook.com / example.com),避免被测系统的邮箱格式校验把人拦住。

用例列表:脚本里存的是占位符,运行时才替换

四、三条真正重要的规则

规则一:同一次运行内,同名变量值一致。

这是最容易做错的地方。系统变量不是「每次出现都随机一次」,而是运行开始时对用到的键求值一次、全程复用:

第 3 步 填写 手机号 → {{randomPhone}}      → 实例化 137****1234
第 9 步 断言 详情页手机号 = {{randomPhone}} → 同一个 137****1234 ✅

如果每处出现都重新随机,断言永远失败,而且失败得没有道理。

规则二:环境变量优先于系统变量。

同名时项目里显式定义的值覆盖内置变量。这是个「劫持开关」:某个环境必须用固定号码(走短信验证码的系统),把 randomPhone 定义成固定值就行,脚本一个字不改。

规则三:存模式,不存实例。

脚本里落库的永远是 {{randomPhone}} 这个占位符,不是它某次求值出来的号码。好处有两个:回放时重新求值 → 每次跑都是新数据,不会「第二次跑就撞已注册」;导出和 Code Review 时看到的是模式,不是一堆随机串。

五、一个真实的坑:fill 写了 A,断言引用了 B

上面三条规则都对,但生成场景里还有一层陷阱,我们是踩到真实案例才发现的:

模型在 fill 那一步没有照要求传占位符,而是自己编了一个值(test829401);等到后面断言里第一次出现占位符时才求值,得到 test442434。两边永远不会相遇 —— 而日志上每一步都是「成功」。

修法不是再嘱咐模型一遍,而是把它变成机制:fill / 组件动作成功后,把「实际写进页面的值」按模板反查,登记为该占位符键的实例化,之后断言和护栏都从这里取值。模型怎么发挥,都不影响一致性。

顺带把和「换值重试」的分工说清:缓存保证同一轮同值,显式换值会重新求值并覆盖缓存 —— 一个保一致性,一个提供不重复,两件事正交。

六、我们没做的几件事

  • 不做类型转换:变量替换就是字符串替换,数字字段要求变量值本身就是合法数字串。
  • 不做全局唯一保证:系统变量是随机,不是序列。需要严格唯一时,用 {{systemTime}} 参与拼接。
  • 不把未定义变量替换成空串(见上文),也让脚本带着 {{xxx}} 显式失败。
  • 不覆盖 sessionStorage / 跨域 iframe:那是浏览器登录态的范畴,交给登录态录制更合适。

七、想听听你们怎么管测试数据

我们这边收敛到「环境变量管共享值、系统变量管唯一值、运行时替换」这一套,但测试数据这块各家做法差别很大。我见过的还有:每个用例独立造数据的接口、测试库定时重置、按用例前缀隔离(case_1024_xxx)、以及直接用线上脱敏数据的快照。

你们团队现在怎么造数据、怎么避免残留数据把回归搞红? 欢迎在评论区聊聊,尤其是那些跑久了才会暴露的方式。

运行日志:未定义变量会明确告警

现状与限制

利益相关:我是 TestDog 的作者,本文写的是自己的实现与踩坑,不构成第三方评测。

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册。