「漫说测试」· 工程实践
一个很香的思路:抓一遍真实请求 → 自动认出"该传递的上下文"(token、id…)→ 直接吐出一份可执行的 YAML 用例,交给测试框架跑。 本篇给完整思路 + 关键算法 + 伪代码 + YAML 用例格式(伪代码即可,重在思路)。
【贴图1】
一、痛点是"上下文",不是"录制"
录制回放的工具不少:抓包存下来、下次重放。但一到真实业务就废——因为请求之间有"上下文依赖":
-
登录接口返回的
token,后面每个请求都要带上; -
创建返回的
order_id,查询/删除时要拼进 URL; -
列表接口里的
id,要用于详情接口……
这些"值传递",靠手抄。 一个流程十几个接口,手抄错一个,用例全废。
一句话:录制的难点不在"存下来",而在"认出谁是谁的上下文"。
【贴图2】
二、整体思路:四步走
① 录制 ──▶ ② 上下文关联 ──▶ ③ 生成 YAML 用例 ──▶ ④ 框架执行
抓真实流量 自动认出变量 带 extract/${} 的用例 YAML 驱动执行器
-
录制
:代理抓一遍真实调用,存成结构化记录(method/path/headers/body/response);
-
上下文关联
:自动识别"跨请求传递的值"——把 A 响应里出现、又被 B 请求带上的值,抽成一个变量;
-
生成 YAML
:把它写成"步骤 + 提取(extract) + 引用(${var}) + 断言"的用例;
-
执行
:YAML 驱动执行器逐步跑,先提取、再渲染、后断言。
【贴图3】
三、核心难点:怎么"自动关联上下文"
这是整件事的灵魂。核心是**"值追踪"**:看一个值有没有"从响应流到后续请求里"。伪代码:
# 关联算法(伪代码)
def correlate(flows, min_len=6):
# 1) 从每个响应里,找出"候选值"(token / id / uuid 等)
candidates = []
for f in flows:
for path, value in extract_scalars(f.response): # 遍历响应 JSON
if looks_like_token_or_id(value) and len(value) >= min_len:
candidates.append((path, value, f.index))
# 2) 看这个值,是否出现在"更晚的请求"里(querystring / body / header)
links = []
for path, value, idx in candidates:
for later in flows[idx+1:]:
for loc in scan(later.request, value): # body/query/header 里搜
links.append({
"value": value,
"name": var_name_from(path), # 变量名:取字段名
"extract_from": {"step": idx, "path": path}, # 从哪一步提取
"use_at": {"step": later.index, "loc": loc}, # 在哪一步引用
})
# 3) 过滤噪声:排除"到处都出现的常量"(base_url、content-type 等)
return dedup([l for l in links if not is_global_constant(l["value"], flows)])
关键启发式(让关联"准"而不"废"):
-
只在"响应 → 更晚的请求"之间关联
,方向固定;
-
排掉全局常量
(出现在所有请求里的值,如域名、固定 header);
-
变量名从来源字段推断
:
$.data.token→token,$.id→id; -
优先长值
(token/id 通常较长),降低误关联。
【贴图4】
四、生成的 YAML 用例长什么样
把关联结果落成一份框架可执行的用例(extract 提取、${} 引用、assert 断言):
# cases/order_flow.yaml —— 由录制+关联自动生成(示意)
case: 下单主流程
context:
base_url: http://127.0.0.1:8000
steps:
- name: 登录
request:
method: POST
path: /login
json: { user: qa, pwd: "secret" }
extract: # ← 自动关联:从响应里"抠"出上下文
token: "$.data.token"
uid: "$.data.user.id"
assert:
- status: 200
- name: 创建订单
request:
method: POST
path: /orders
headers:
Authorization: "Bearer ${token}" # ← 自动关联:引用上一步的 token
json:
item: apple
qty: 2
extract:
order_id: "$.id"
assert:
- status: 201
- name: 查询订单
request:
method: GET
path: /orders/${order_id} # ← 引用创建返回的 id
assert:
- status: 200
- jsonpath: "$.status == 'created'"
- name: 清理(删订单)
request:
method: DELETE
path: /orders/${order_id}
assert:
- status: 204
亮点:
extract/${}这些关联,是"自动关联"帮你填的——你只录了一遍,它替你把"值传递"接好了。
【贴图5】
【贴图6】
五、框架怎么执行这份 YAML
一个极简的 YAML 驱动执行器(伪代码):先提取 → 再渲染 → 后断言。
# runner(伪代码)
def run(case):
ctx = dict(case.get("context", {}))
for step in case["steps"]:
req = render(step["request"], ctx) # 把 ${var} 替换成真实值
resp = http_send(ctx["base_url"], req) # 发出去
check_asserts(resp, step.get("assert", [])) # 断言(状态码 / jsonpath)
for name, expr in step.get("extract", {}).items():
ctx[name] = jsonpath(resp.json(), expr) # 提取进上下文,供后面用
return "PASS"
执行器要做好的几件事:
-
${}渲染:从上下文里取值替换(header / url / body 都要支持);
-
extract:按 JSONPath/正则从响应里取值,存入上下文;
-
断言
:状态码 + JSONPath 表达式(
$.status == 'created'); -
失败即停 / 记录
:某步失败,报告在哪一步、期望 vs 实际。
【贴图7】
六、伪代码:从"录制"到"出 YAML"(串起来)
# 1) 录制:mitmproxy addon(伪代码)
class Recorder:
def response(self, flow):
store.append({
"index": len(store),
"request": {"method": flow.request.method,
"path": flow.request.path,
"headers": dict(flow.request.headers),
"body": flow.request.get_text()},
"response": {"status": flow.response.status_code,
"body": flow.response.get_text()},
})
# 2) 关联(见第三节 corelate)
# 3) 生成 YAML(伪代码)
def to_yaml(flows, links):
steps = []
for f in flows:
step = {"name": guess_step_name(f),
"request": f["request"],
"assert": [{"status": f["response"]["status"]}]}
# 把"命中关联"的值,替换成 ${var},并在来源步骤补 extract
for l in links:
if l["use_at"]["step"] == f["index"]:
replace_in(step["request"], l["value"], "${%s}" % l["name"])
attach_extract(steps[l["extract_from"]["step"]], l)
steps.append(step)
return {"case": "auto", "steps": steps}
【贴图8】
七、几个必须处理的边界
-
动态/随机值
(时间戳、随机数):无法关联,用变量/占位符或生成器替代,别硬比;
-
敏感信息脱敏
:录制里可能带真实凭据/Cookie——入库前脱敏,别把生产凭据写进 YAML;
-
正负向用例
:录制得到的多是"happy path",负向(越权/异常)要人工补;
-
别直接把录制当"回归"
:关联错的变量会"假绿",生成后要人工审一遍;
-
接口变了要重录
:录制是"快照",接口一变,关联可能失效。
【贴图9】
八、给测试工程师的 3 条落点
-
录一遍,省一半
:主要流程先靠"录制 + 关联"铺起来,人手留给负向和判断;
-
YAML 是可审计的资产
:用例是人可读的文件,比黑盒脚本更好 review、更好维护;
-
关联要"宁缺毋滥"
:宁可人工补几个变量,也别自动关联出一堆错值——假绿比红灯更可怕。
一句话:录制负责"跑一遍先记下来",关联负责"把它接成一条线",YAML 负责"让框架能跑"——而测试人,负责判断"它到底对不对"。
【贴图10】
🎁 加料包:《接口录制→YAML 用例》实践手册
上面是思路。我把完整方案整理成了手册:关联算法细节 + YAML 用例 Schema + 执行器伪代码 + 录制 addon 伪代码 + 边界处理清单。
公众号后台回复「record」,领取《接口录制→YAML 用例实践手册》。
附:一句话流程
抓一遍(mitmproxy) → 关联(值追踪) → 生成 YAML(extract/${}) → 执行器跑
本文为思路/伪代码分享;关联算法与 YAML 格式可按团队框架调整。