Industry & PracticeResearch & Benchmarks
留 出 集 关键 字 段 F1 为 85.42 %: 字 节 用 单 条 流量 生成 REST 断言
229 条没见过的接口上,关键字段 F1 85.42%(精确率 81.30%,召回 96.15%)。线上采用率在全量上线前是 74.1%,12 月稳定在 96% 以上。图注末段却写成 97.2%,开头还有 86.1%。

In this piece
留出集关键字段 F1 为 85.42%:字节用单条流量生成 REST 断言
复旦大学与字节跳动的论文 RESTOR: Automated Test Oracle Generation for RESTful APIs via Reinforcement Learning。arXiv 为 2607.23963v1,HTML 水印写着 2026 年 7 月 27 日。abs 页记录提交日是同一天。许可证是 CC BY 4.0。期刊元数据里的 Received 2026-04-16 是收稿日,不是公开日期,本文用提交日。稿件被 ISSTA 2026 接收。作者从 Xun Zhou 起,通讯作者 Zhen Dong 在复旦。字节侧是 Qiang Li、JunJie Li、Sifan Wang、Xiaolong Yu,邮箱分别是 liqiang.leo@bytedance.com、wells.li@bytedance.com、wangsifan.28@bytedance.com、yuxiaolong.1@bytedance.com。
RESTOR(论文正文也写成 Restor,用强化学习、只靠一条流量给 REST 接口生成测试预言的框架)处理的是测试预言(判断这次返回算不算对的可执行断言)。REST 在这里指用 HTTP 方法和 JSON 交换数据的接口。目标不是再造一遍请求,而是在没有 OpenAPI、也没有历史日志时,从一对请求和响应写出 Python 断言。
冷启动只有一条流量
论文把场景写成敏捷团队的冷启动:文档经常缺、旧或没更新,新接口还攒不出成批日志。质量工程师有时只能拿一次冒烟测试留下的请求-响应对,自己判断哪些字段该钉死、哪些字段每次都会变。
只靠规格的预言工具,规格缺失就停。从大量执行日志里挖不变量的方法(文中对照 AGORA+,用历史流量估计字段该长什么样)在一条样本上站不住。直接调用大参数通用模型,延迟和费用不适合每天跑几千次的流水线,而且容易把追踪号、时间戳也断言进去,测试变得不稳定。RESTOR 改成微调一个轻量模型,让它学会测试里的常识:盯住稳定、有业务含义的字段,放过动态噪声。
先标关键字段,再造正负样本
训练数据来自字节内部的接口录制平台。摘要写的是 246 个真实服务上超过 2300 条流量。第 5.1.2 节写得更细:超过 2300 条不重复流量,来自超过 1500 个 REST 接口,每个接口 1 到 3 条,跨 246 个服务、15 条业务线,从内容管理到计费。流量先匿名化,去掉可识别个人的信息。按 8:1:1 随机分成训练、验证、测试。主评测用留出测试集,229 条没见过的接口样本。
每条样本是六元组:接口、请求、响应、关键字段集合、正样本响应、负样本响应。关键字段由字节三名专业质量工程师独立标注,至少两人同意才留下。三条原则:能唯一标识业务实体的字段,例如 product_id;表达领域约束的字段,例如 begin_time、end_time、subscribe_type;表示执行结果的字段,例如 ret、errmsg。log_id 和服务器时间 systime 明确排除。
没有规格,就不能直接知道模型写出的断言对不对。同一组三人再为每个关键字段写自然语言约束,仍用多数表决。然后按约束替换字段值:每个关键字段三个合法值、三个非法值。合法替换构成正样本,用来防止断言死记这一次的具体数字。非法替换构成负样本,例如时间戳写成 -1,布尔字段写成字符串 "true"。状态码这类只有一个正确值的字段,不造正样本。表 1 的例子:ret 成功应为 "0",正样本仍是 "0",负样本是 "1";subscribe_type 只能是 "auto" 或 "un-auto",负样本写成 "manual"。
标关键字段大约 4 天。写语义约束并造正负样本大约两周。
奖励先看能不能在原响应上跑过
策略网络是 Doubao-Seed-1.6-flash(字节内部的轻量模型,用来逐 token 生成断言代码)。微调算法是 GRPO(Group Relative Policy Optimization,用同一提示下一组输出的相对奖励更新策略,不另训一个价值网络)。训练 2 个 epoch,学习率 \(1\times10^{-6}\),KL 系数 0.001,裁剪比 0.2。每个提示采样 8 条输出,用组内奖励的均值和标准差算优势。因为强化学习微调贵,训练和评测都只跑了一遍。
一条断言的奖励是有门槛的。先在原始响应上执行:抛错或断言失败,奖励直接是 -1,后面不再算。能跑过,才把两部分加权后裁剪到 \([-1,1]\):
- 字段是否对上。断言代码碰到的字段集合和人工关键字段做精确率、召回率,再加权相加。精确率惩罚乱断言,召回率惩罚漏掉该查的字段。权重的具体数字论文没有给。
- 语义是否分得开。杀伤率是一组样本里断言判为失败的比例。负样本杀伤率越高越好,正样本杀伤率越高越差。理想断言在正样本上通过率为 1,在负样本上通过率为 0。
两部分的系数同样没有给数值。论文把这个设计说成:奖励的是能接受合法波动、又能抓住违约,而不是把某一次响应抄下来。
对照用同一条提示词,并且关掉推理
评测挂在字节内部的用例生成平台上。工程师上传匿名流量,系统给出带断言的 Python 测试脚本,人改完或接受后再提交进版本库。三个问题:相对基线,预言质量如何;专家觉得有没有用;上线后采用率如何。
基线只有提示工程,没有强化学习微调,系统提示和输入格式与 RESTOR 相同,推理能力关掉。两个基线:
- Doubao-Seed-1.6-flash 的预训练原模型。它就是策略网络的起点,用来看 GRPO 多出来的部分。
- DeepSeek-V3.1-Terminus(文中简称 DeepSeek,从 DeepSeek-V3 来的大参数通用模型)。用来看轻量专用模型和大通用模型的差别。
指标分三层。关键字段:精确率、召回率、F1。断言对错按专家复审分成五档:完全匹配、过宽、部分匹配、错误、未覆盖。线上采用率是工程师接受并提交进仓库的生成任务数,除以生成任务总数。一次任务等于上传一条请求-响应流量、发起一次生成。
229 条留出样本上,F1 最高,漏检最少
关键字段上,RESTOR 精确率 81.30%,召回率 96.15%,F1 85.42%。DeepSeek 召回率更高,98.64%,精确率只有 67.57%,容易给无关字段也写断言。原模型精确率 68.54%,F1 77.29%。原模型的召回率和 DeepSeek 的 F1,正文没有单列数字。单侧 Wilcoxon 符号秩检验,\(N=229\),置信水平 0.95。精确率和 F1 上,RESTOR 对两个基线都是 \(p<0.001\)。DeepSeek 平均召回略高,但精确率低,F1 仍然更差。论文没有把召回率的差异说成显著。
断言五档里,正文给出的是完全匹配和未覆盖。完全匹配:RESTOR 663 条,DeepSeek 622 条,原模型 549 条。该覆盖却没写断言:原模型 122 个字段,DeepSeek 43 个,RESTOR 28 个。错误断言被说成很少,但 RQ1 没有给出错误条数。动态字段在训练和推理时被滤掉,剩下的误报靠人审:工程师提交前还要改。论文没有报告这轮人审改了多少行。
个案用 POST /api/subscription/plan_list。表 2 把字段路径收成 val,并省掉 assert。subscribe_type 的约束是只能取 "auto" 或 "un-auto"。原模型没写这条断言。DeepSeek 写成 len(val) > 0,过宽。RESTOR 写成 val in ['un-auto', 'auto']。space_info.quota 应是表示非负整数的字符串。原模型只查键在不在。DeepSeek 写成 int(val) >= 0,算部分匹配。RESTOR 同时查是字符串且整数值非负。begin_time 应是非负 Unix 时间戳,RESTOR 只写成 val >= 0,论文自己标成部分匹配,没有标成完全匹配。systime 和 log_id 应忽略。原模型和 RESTOR 忽略了。DeepSeek 对它们写出 int(val) > 0 和 len(val) > 0,算误报。
48 条真实上传上,F1 掉到 69.91%
RQ2 不用那 229 条精标样本,而从平台上随机抽 48 条用户上传的流量。这些响应更乱。专家标了他们认为该查的字段,也写了对断言松紧的偏好。
RESTOR 精确率 60.27%,召回率 94.54%,F1 69.91%。DeepSeek 召回率 93.13%,精确率 51.67%。完全匹配条数:RESTOR 216,DeepSeek 203,原模型 153。错误断言:RESTOR 3 条,DeepSeek 15 条。原模型的错误条数正文没给。图注把 DeepSeek 的精确率均值说成大约 50%,RESTOR 大约 60%,和正文的 51.67%、60.27% 同向,但不是同一个写法。
另外做了定性访谈:13 名质量工程师,跨 11 条业务线。四项是相关性、准确性、生成速度和体感、开放反馈。归纳成四条:基线常做“键是否存在”这种浅检查,RESTOR 的断言更深,少改一点才能用;相对大模型,生成更快,断言一多时更明显,没有给出毫秒数;48 条里有 3 条响应字段超过 400,断言变长,训练分布通常不到 100 个字段,这 3 条要人手整理;计费规则这类专有约束,黑盒单条流量推不出来,论文把它写成以后用提示词补,而不是这次的结果。
采用率在 11 月 8 日之后抬上去
线上从 2025 年 11 月初开始记。时间窗是 2025 年 10 月中到 12 月中,五个双周。正文写:微调模型全量上线前、11 月 8 日之前,采用率 74.1%;上线后第一个区间到 92.6%;12 月稳定在 96% 以上。摘要写的是从 74.1% 提到 96% 以上。图 7 的说明不一样:曲线从 86.1% 起,掉到 74.1%,最后一段 97.2%,并写最后一段稳定在 97% 以上。结论又写成核心业务线上持续超过 90%。这四个数不是同一口径,正文、摘要、图注、结论不能互相替代。
按业务线,图 8 画了 BL01 到 BL10 十条。一次任务仍是上传一条流量并请求生成。正文只展开两条主负载:BL02 采用率 97.0%,1064 次任务;BL03 采用率 93.2%,117 次任务。图注写 BL01 为 100%、BL10 为 0%,任务量用对数轴,BL02 是大头。BL04 到 BL10 量小、波动大。论文给的原因是两条:有的线逻辑重、超出模型范围;有的团队还在接入。数据集写的是 15 条业务线,这张图是 10 条,访谈是 11 条。三条线的分母不一样。
这些数不能被读成什么
85.42% 是 229 条留出样本上的关键字段 F1,不是断言完全匹配率,也不是线上采用率。完全匹配的 663、622、549 是断言条数,论文没有给出每边的断言总数,所以不能把 663 换成百分比。48 条真实上传上 F1 是 69.91%,精确率从 81.30% 掉到 60.27%。线上那个 96% 是人接受并提交的比例,不是缺陷检出率。
采用率的定义依赖人点提交。论文自己把威胁写成:人可能没细看就提交。缓解说法是字节流程要求同行评审,提交意味着作者和评审人都看过。它仍是代理指标。11 月 8 日前的 74.1%、上线后的 92.6%、12 月的 96% 以上、图注里的 86.1% 与 97.2%、结论里的 90%,同时出现。缺了每个双周的任务分母,读不出提升了多少次提交。
训练和评测只跑一遍,没有方差。基线关掉了推理,这组差距不能外推到打开推理的大模型。奖励里的系数没有数值。RQ1 的错误断言没有计数。原模型召回率、DeepSeek 的 F1 也没有单列。
测试集的接口端点从训练集里物理拿掉。RQ2 用的是还没进录制平台的新功能流量,用来避免背题。标注仍是字节自己的三名工程师,多数表决,不是外部标注。数据全部来自字节,15 条业务线、246 个服务只说明内部多样性,不说明别的公司信封格式也能直接用。断言只生成 Python。代码、原始流量和训好的模型因保密不公开。附录提示词写了“结合一些历史流量”,方法部分写的却是单条请求-响应对。两处口径不一样。
出处:字节跳动、复旦大学,Xun Zhou、Zhen Dong 等,RESTOR: Automated Test Oracle Generation for RESTful APIs via Reinforcement Learning,2026-07-27,https://arxiv.org/abs/2607.23963
Found it useful? Pass it on
Scan with WeChat to open it on your phone and forward it.