Files
deepseek-harness/docs/rfc/implemented/testing/2026-06-11-property-based-testing.zh.md
Ziya 2565133af3 docs(i18n): RFC tree batch — 146 bilingual pairs via the committed pipeline
implemented(除 4 篇超长文档随后补)、proposed、rejected 全树配对;
同一流水线 + 二遍校验(paraphrase-back + 仓库上下文一致性)产出。
docs/rfc/implemented/AGENTS.md 与其 CLAUDE.md 符号链接列入排除
(agent 指令文件,与根 AGENTS.md 同策略)。
2026-07-15 23:25:06 -07:00

3.3 KiB
Raw Blame History

RFC:对协议形态代码进行基于属性的测试

Status: implemented

English | 中文

将原始提案与决策记录合并为一篇。首次运行即发现了 BlockAssembler 的重复 block-end 真实 bug。

问题

基于示例的测试只能固定我们想到的用例。harness 的核心是协议形态的代码:分片流、事件日志、schema 转换、收件箱调度。这类代码的输入空间是组合爆炸的,有趣的 bug 藏在没人写过示例的交错序列里。佐证:一个 block 组装的排序 bug 曾在 happy path 100% 行覆盖率下存活。逐文件 100% 覆盖率只能证明每行都跑过,不能证明每种交错都正确。

决策

引入 fast-check(根 devDependency),在每个协议形态的包中编写一个 tests/properties.spec.ts。生成器调优为逼真但对抗性的输入(而非均匀噪声),numRuns 控制在本地套件总耗时远低于约 10 秒。失败时打印可复现的 seed。(原始提案还草拟了一个夜间 CI job,以 100 倍迭代运行;该部分未交付——属性测试套件仅在常规 push/pull_request CI 中运行,定时高迭代 job 仍属可能的后续工作。)

  • dsh-llm / BlockAssembler: 任意分片流(合法 + 畸形:重复索引、滞后分片、缺少 block-start)。不变式:blocks() 数量 ≤ 出现过的不同索引数;重组幂等(blocks() 在重复调用间稳定,且 message().content 与之一致);blocks() 从不抛异常且只产出合法的 content-block 标签;finish 反映最后一个 finish 分片,无 finish 分片时默认为 {kind:'stop'}。
  • dsh-session: 任意事件日志。不变式:deriveMessages 确定性;从 seed 回放结果一致;seq 严格单调递增;非消息事件不影响派生历史;派生内容与日志解耦。
  • dsh-tools: 任意 SchemaSpec。不变式:JSON Schema 的 required 等于每层 required:true 的键集合;转换是全函数;并且与运行时参数校验组合验证——满足 spec 的生成参数通过 validateArgs,定向破坏(删除 required 键、顶层非 object)被拒绝。这封堵了 validator 与 InferArgs 漂移的风险。
  • dsh-agent-loop: 任意发送调度,对接一个永不耗尽的适配器,通过 agent/status settle 信号驱动(无挂钟 sleep)。不变式:无消息丢失;轮次编号严格递增;状态转换始终在合法状态机上。

后果

  • 生成器质量是价值杠杆——生成器偏向小索引池和短字符串,使碰撞与交错频繁出现。
  • 已经产出回报: BlockAssembler 的流测试发现了一个真实 bug——同一索引的重复 block-end 覆写了已刷出的块,导致流式前缀与最终 blocks() 不一致。已修复(首次关闭生效,与既有的滞后分片规则一致),并附带专门的回归测试。
  • 属性测试因超时而 flake 是一个发现,不应重试了事。agent loop 的属性测试在设计上是确定性的(通过 agent/status settle),因此挂起即为真实缺陷。
  • 属性测试是示例测试的补充而非替代;示例测试固定特定分支,服务于 100% 覆盖率门禁。