「该手机号已注册」「设备名称已存在」「订单号重复」—— 写 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 位身份证号(含校验位) | 直接填证件号字段 |
「随机」这两个字在测试里是要讲质量的,几个细节:

规则一:同一次运行内,同名变量值一致。
这是最容易做错的地方。系统变量不是「每次出现都随机一次」,而是运行开始时对用到的键求值一次、全程复用:
第 3 步 填写 手机号 → {{randomPhone}} → 实例化 137****1234
第 9 步 断言 详情页手机号 = {{randomPhone}} → 同一个 137****1234 ✅
如果每处出现都重新随机,断言永远失败,而且失败得没有道理。
规则二:环境变量优先于系统变量。
同名时项目里显式定义的值覆盖内置变量。这是个「劫持开关」:某个环境必须用固定号码(走短信验证码的系统),把 randomPhone 定义成固定值就行,脚本一个字不改。
规则三:存模式,不存实例。
脚本里落库的永远是 {{randomPhone}} 这个占位符,不是它某次求值出来的号码。好处有两个:回放时重新求值 → 每次跑都是新数据,不会「第二次跑就撞已注册」;导出和 Code Review 时看到的是模式,不是一堆随机串。
上面三条规则都对,但生成场景里还有一层陷阱,我们是踩到真实案例才发现的:
模型在
fill那一步没有照要求传占位符,而是自己编了一个值(test829401);等到后面断言里第一次出现占位符时才求值,得到test442434。两边永远不会相遇 —— 而日志上每一步都是「成功」。
修法不是再嘱咐模型一遍,而是把它变成机制:fill / 组件动作成功后,把「实际写进页面的值」按模板反查,登记为该占位符键的实例化,之后断言和护栏都从这里取值。模型怎么发挥,都不影响一致性。
顺带把和「换值重试」的分工说清:缓存保证同一轮同值,显式换值会重新求值并覆盖缓存 —— 一个保一致性,一个提供不重复,两件事正交。
{{systemTime}} 参与拼接。{{xxx}} 显式失败。我们这边收敛到「环境变量管共享值、系统变量管唯一值、运行时替换」这一套,但测试数据这块各家做法差别很大。我见过的还有:每个用例独立造数据的接口、测试库定时重置、按用例前缀隔离(case_1024_xxx)、以及直接用线上脱敏数据的快照。
你们团队现在怎么造数据、怎么避免残留数据把回归搞红? 欢迎在评论区聊聊,尤其是那些跑久了才会暴露的方式。

本项目已收录进 TesterHome 社区开源项目库:TestDog。
帮助文档 · 测试数据与登录态:https://softwing.top/testdog-doc/guide/test-data.html
快速开始:https://softwing.top/testdog-doc/guide/getting-started.html
利益相关:我是 TestDog 的作者,本文写的是自己的实现与踩坑,不构成第三方评测。