24 · 可测试性与自动化验证
24 · 可测试性与自动化验证
核心命题:系统必须能被自动化验证,而不是靠人肉试玩。 小团队没有 QA 部门,唯一出路是让系统"自己证明自己是对的"。 相关:
systems/08(蒙特卡洛与机器人跑测)、systems/09(平衡工作流)。
1. 可测试性是设计属性,不是事后补的
如果系统无法被测试,那是设计问题,不是测试问题。
| 设计原则 | 做法 | 反面 |
|---|---|---|
| 确定性 | 同样的输入 + 同样的种子 = 同样的输出 | 依赖帧率、随机无种子、时间依赖 |
| 参数外置 | 所有数值在外部数据源 | 魔数散落在代码里 |
| 可观测 | 任何系统状态可被查询/打印 | 状态只在内部,只能靠眼睛看 |
| 可注入 | 能注入假输入、假时间、假随机 | 强耦合到引擎/UI |
| 可跳过 | 能跳过表现层,只跑逻辑 | 逻辑与动画绑定(跑测试要等动画) |
| 无副作用 | 同样的操作可重复执行 | 全局单例状态污染 |
最快收益的两条:确定性 + 参数外置。有了这两条,90% 的自动化才可能。
2. 三层测试金字塔
| 层 | 测什么 | 成本 | 数量 | 频率 |
|---|---|---|---|---|
| L1 逻辑单元测试 | 单条公式/规则 | 极低 | 多(数百) | 每次提交 |
| L2 系统模拟测试 | 完整循环、经济、平衡 | 中 | 中(数十) | 每次构建 |
| L3 集成与人工 | 手感、体验、可读性 | 高 | 少 | 每迭代 |
关键判断:凡是能用代码验证的,绝不靠人看。
- 能自动化:数值范围、可解性、经济守恒、约束违反、回归
- 只能人工:手感、情绪、可读性、乐趣
L1 示例(应写成自动化断言):
assert damage(level=1, weapon=base) == 10
assert damage(level=100, weapon=base) < damage(level=100, weapon=epic)
assert dr(armor=0) == 0 and dr(armor=K) == 0.5 // 半效点
assert ehp(hp=100, armor=K) == 200 // EHP 线性
3. 模拟 Harness(最重要的投资)
定义:一个不渲染、只跑逻辑的测试环境,让机器人以 N 倍速玩你的游戏。
| 能力 | 用途 |
|---|---|
| 无头运行 | 跳过渲染/动画,10–1000× 速 |
| 脚本化策略 | 机器人按"最优/随机/贪心/新手"策略玩 |
| 批量跑 | 一次跑 10⁴ 局 |
| 数据导出 | 每局的资源曲线、通过率、build、死亡点 |
| 种子固定 | 版本对比时可复现 |
三种机器人策略(至少实现前两种):
| 策略 | 用途 |
|---|---|
| 最优 | 找出理论上限与支配策略 |
| 随机 | 测下限,找"必死局" |
| 新手模拟 | 按新手行为模式(选第一个/不探索)测 FTUE |
投资回报:harness 的建设成本通常 3–10 人日,但能替代数百小时的试玩。 卡牌/自走棋/自动化类游戏必须做——组合空间人手测不完。
4. 回归测试与黄金数据(Golden Run)
问题:改了一个数值,怎么知道没破坏别的东西?
做法:记录一份"黄金数据"(基准结果),每次构建后重跑并对比差异。
golden_run_v1.3.json
平均 TTK: 4.2s
通关率: 36%
平均单局时长: 24min
各 build 使用率: [...]
资源存量@10h: {...}
对比规则:
- 差异 < 5% → 通过
- 差异 5–15% → 警告,人工确认是否符合预期
- 差异 > 15% → 失败,必须解释(预期内改动需更新黄金数据)
黄金数据必须带版本标签,并在有意改动后主动更新(而不是被绕过)。
5. 平衡自动化扫描
每次构建自动跑一遍,输出三张表的红项(见 systems/09 §2):
| 扫描 | 自动判定 |
|---|---|
| 使用率扫描 | 使用率 >60% → 支配策略; <5% → 废卡 |
| 通过率扫描 | 任何关卡 <20% 或 >90% → 断崖/无挑战 |
| 经济扫描 | 资源单调上升 → 通胀;Payback 某层 >2× 中位 → 沙漠 |
| 可解性扫描 | 任何生成结果无解 → 阻断级 bug |
| 一致性扫描 | 显示值 vs 实际值不符 → 信任 bug |
输出形式:一份可读的报告,标红项直接进 backlog。 目标:让"平衡问题"在被人发现之前被机器发现。
6. 系统侧 Bug 高发区(优先测这些)
| 高发区 | 典型 bug | 测试方式 |
|---|---|---|
| 边界条件 | 数值为 0/负数/上限 | 参数化边界测试 |
| 状态叠加 | 多个增益同时生效顺序错误 | 组合矩阵测试 |
| 存档/读档 | 读档后状态不一致 | 存档往返测试(存→读→比对) |
| 时间依赖 | 暂停/加速/切后台导致计时错乱 | 时间注入测试 |
| 随机 | 无种子、随机不可复现 | 种子复现测试 |
| UI 与逻辑脱钩 | 显示的值 ≠ 实际的值 | 一致性断言 |
| 生成 | 不可解、卡死 | 批量生成 + 求解器验证 |
| 经济 | 溢出、负数、无限循环套利 | 长时模拟(100h) |
| 本地化 | 字符串溢出、占位符丢失 | 最长语言膨胀测试 |
| 并发/网络(若适用) | 状态不同步 | 回放测试 |
存档往返测试是最容易被忽略、后果最严重的:玩家丢档 = 差评 + 退款。 必须自动化:存 → 读 → 全状态比对。
7. QA 分层与验收
| 层 | 谁做 | 频率 | 关注 |
|---|---|---|---|
| 自动化 | 机器 | 每次提交/构建 | 逻辑、数值、回归、可解性 |
| 开发自测 | 开发者 | 每日 | 功能是否可用、边界 |
| 内部试玩 | 全团队 | 每周 | 手感、节奏、明显问题 |
| 外部试玩 | 招募玩家 | 每迭代 | 体验、理解、乐趣(见 production/16) |
| 出货验收 | 全员 | 出货前 | checklists/01 G4 |
关键分工:
- 自动化回答"对不对"
- 人工回答"好不好"
- 不要用人工去查机器能查的东西(浪费),也不要用机器去判断乐趣(做不到)
8. 反模式
| 症状 | 修法 |
|---|---|
| 所有验证靠人肉试玩 | 建 harness,把可自动化的自动化 |
| 随机无种子 | 一切随机源自种子 |
| 逻辑与动画绑定 | 逻辑层可独立运行(无头模式) |
| 数值散落在代码里 | 参数外置 |
| 无回归基准 | 建黄金数据 + 版本标签 |
| 改了数值不知道影响了什么 | 每次构建跑全量扫描 |
| 存档往返未测 | 自动化:存→读→比对 |
| 只在"功能完成"时测 | 每次提交跑 L1 |
| 用人工查数值边界 | 参数化边界测试 |
| 黄金数据被绕过(失败就更新) | 差异 >15% 必须解释原因 |
| 无 headless 模式 | 优先实现,是 harness 的前提 |
| 生成结果不验证可解性 | 求解器自动验证,阻断级 |
9. 交付物
- 可测试性检查表(确定性/参数外置/可观测/可注入/可跳过)
- 模拟 harness 规格(无头模式 + 三种机器人策略 + 数据导出)
- 黄金数据基准文件(带版本标签)
- 自动化扫描报告模板(五类扫描 + 红项)
- 边界条件测试清单(按系统)
10. 参考
systems/08-numbers-and-formulas.md(蒙特卡洛与机器人跑测)systems/09-balance-workflow.md(三张诊断表)production/16-prototyping-and-playtest.md(人工试玩的正确用法)