更新:2026-06-29。基于 FEEDADS-24228 Android 动态号拨号链路自测实践整理。重点不是讨论某个测试术语,而是说明一种复杂链路需求的交付方法:先把需求写成可验证的 Spec,再用用例、Mock、脚本和证据完成闭环。
一、什么是 Spec?
Spec 是 Specification 的缩写,可以理解为“规格说明”。在工程语境里,它不是普通需求描述的同义词,而是把目标、边界、约束和验收标准写成可以指导实现的结构化材料。
Coding Spec / Spec-Driven Development 在 AI 编程工具中通常指以结构化规格驱动实现:先形成需求、设计、任务等中间产物,再由 AI coding agent 基于这些产物执行编码和任务推进。
GitHub Spec Kit、Kiro specs 都是这个方向的例子。前者把流程拆成 specify、plan、tasks、implement 等阶段;后者会生成 requirements.md、design.md、tasks.md,分别承载需求与验收标准、技术设计和实施任务。
普通需求更常回答“要做什么”,例如“支持动态号拨号”“兼容新协议”“异常时兜底真实号”。Spec 需要继续回答“在什么条件下做什么”“哪些情况不能做”“怎样判断做对”。也就是说,Spec 的价值不是把需求写得更正式,而是把模糊目标变成实现和验收可以共同对齐的依据。
对 AI 编程来说,这一点更重要。AI 可以很快生成代码,但如果输入只是宽泛目标,生成结果往往看起来合理,却很难判断是否覆盖了所有边界条件。
二、AI 编程中的 Spec-Driven Development
Spec 解决的是“AI 按什么开发”的问题。它降低了自由提示带来的歧义,但还没有完全解决另一个问题:AI 生成的代码如何证明符合 Spec。
对于协议型、链路型需求,主路径可运行只能说明某条调用链被触发,不能证明字段映射、协议选择、异常兜底和日志上报都符合预期。
三、可验证 Spec:把需求变成可断言规则
可验证 Spec 是更面向交付判断的一层规格。它不只描述目标,也明确每条规则的触发边界、判断条件、预期行为和通过证据。
例如需求里写:
支持动态号取号和拨号上报。
这只能说明目标。更适合自测和评审的写法是:
当numberProtocol=2且numberPostBody非空时,取号接口必须使用 POST;当logProtocol=2且logPostBody非空时,上报接口必须使用 POST;当 POST body 为空时,不应继续发送对应请求。
后面这句话已经具备验证条件、预期行为和反向约束,可以直接转换成用例、Mock 断言和 CR 检查点。![]()
| 层次 | 回答的问题 | 动态号拨号示例 |
|---|---|---|
| 需求目标 | 要实现什么 | 支持动态号拨号,异常时兜底真实号 |
| Spec 规则 | 什么条件下应该发生什么行为 | numberProtocol=2 且 body 非空时,/number 必须使用 POST |
| 验证用例 | 用什么输入覆盖规则 | 构造 body 非空、body 为空、取号异常等 case |
| 证据标准 | 如何复查是否通过 | Mock 请求方式、拨号盘号码、logcat、toast 表现 |
验证规则和 Spec 不是两个并列概念。单条验证规则是 Spec 的组成单位;一组规则加上入口、条件、异常和证据标准,才形成可验证 Spec。
四、验证闭环:用例、Mock、脚本、证据
可验证 Spec 真正落地时,不是再多写一份说明,而是把规则放进一条闭环里:
| 环节 | 作用 | 产物 |
|---|---|---|
| 用例矩阵 | 把抽象规则拆成具体输入、返回和预期行为 | TC01、TC02、异常 case、空 body case |
| Mock Server | 固定后端返回和外部接口波动 | /number、/log、不同 mock case |
| Scheme 脚本 | 固定端上触发参数,避免手拼出错 | 可重复执行的 adb shell am start 命令 |
| 证据记录 | 支撑通过、失败或差异判断 | mock 日志、拨号盘号码、logcat、toast、无请求窗口 |
这四个环节分别解决不同问题:用例矩阵回答“测什么”,Mock 回答“外部变量怎么固定”,脚本回答“输入怎么复现”,证据回答“结论怎么复查”。
最终目标不是证明“我测过”,而是让别人可以顺着 Spec、用例、输入和证据重新判断是否符合预期。
五、边界:什么时候值得这样做?
这套方法适合用于规则多、入口多、依赖多、分支多的需求,尤其是协议型需求、端能力链路、SDK 能力、后端依赖不稳定、兜底分支多、CR 修复点明确的场景。
它不适合非常小的 UI 文案改动、一次性临时修复,或者没有明确验收标准的探索任务。完整闭环有成本,只有当“测清楚”比“测一下”更重要时,它才值得使用。
它也不是单纯 QA 测试。QA 测试通常发生在实现之后,而这里的用例设计是前置的,会参与需求理解、实现校验和交付判断。更准确地说,这是一种可验证交付驱动:先定义正确性,再让实现、自测和文档围绕这个定义推进。
六、用 FEEDADS-24228 看 Mock 自测方案
FEEDADS-24228 的 Android 动态号拨号链路,可以作为这套方法的完整例子。需求目标是支持动态号拨号并兼容新协议,但实际链路同时涉及手百 makePhoneCall 入口、SDK vendor/ad/call 能力、nadcore 外部桥接、动态号取号、拨号上报、GET/POST 协议选择、号码兜底和 toast 展示。
这个案例的 Mock 自测方案并不依赖真实广告物料和真实后端,而是用本地 mock server 固定后端返回,用 scheme 脚本固定端上输入,让真机只验证端上动态号拨号规则是否符合 Spec。

这张图里的方案可以拆成四个关键点:
| 验证环节 | FEEDADS-24228 中的落点 |
|---|---|
| Spec 规则 | GET/POST 协议、空 body 拦截、号码兜底、上报条件 |
| 固定输入 | make_feedads_24228_scheme.py 生成 numberProtocol、logProtocol、body、真实号和 mock case |
| 固定外部依赖 | feedads_24228_mock_server.py 控制 /number 返回动态号、虚拟号、空数据、异常 JSON 或服务端错误 |
| 执行链路 | adb shell am start 拉起 makePhoneCall 或 vendor/ad/nadcore/call |
| 证据判断 | mock 日志、拨号盘号码、logcat、toast 表现、无新增请求窗口 |
这个例子的关键不是 mock server 本身,而是它把“AI 按 Spec 写了代码”进一步推进成“每条规则都有可执行输入和可复查证据”。这也是复杂端侧需求里最容易缺失的一环。
七、核心结论
复杂链路里,自测的关键不是测更多遍,而是先把“正确”定义清楚。
可以把这套实践压缩成三句话:
先写 Spec,再写例子。
先固定变量,再执行链路。
先沉淀证据,再说交付。
最终交付的不是一句“我本地测过了”,而是一套别人能理解、能复查、能复用的验证材料。高质量自测不是把测试放到最后补,而是从一开始就让需求变得可验证。