游戏测试和普通测试到底差在哪 —— 读《游戏测试完全指南》第 1 章
上篇写了《全栈测试》的读书笔记——那是普通软件的测试方法论。这篇开始读一个专门讲游戏测试的教程,22 章,从人工测试讲到强化学习代理。第一章是基础理论。
读之前我有个直觉上的问题:**”游戏测试和普通测试,不就差一个’测的东西是游戏’吗?”**
读完第一章发现不是。差的东西很具体,而且每一条都直指游戏代码和普通应用代码的底层差异。以下是我理解后的梳理。
一、交互复杂性:不是输入 → 输出,是持续碰撞
普通软件的测试模型是清晰的:输入 A → 处理 → 输出 B。测试用例就是”给定 A,验证结果是 B”。
游戏不是这样。游戏的每一帧都在发生多系统的交叉作用。以我写的那个 Go 射击游戏为例,一帧里同时跑的东西:
1 | 帧 N: 玩家按空格 → 射击冷却检查 → 生成子弹 → 注册碰撞事件 |
这不是一条线性链路,是网状交互。教程里给了个 MMO 战斗的复杂度公式:
$$C_{combat} = N_{skills} \times N_{buffs} \times N_{targets} \times N_{positions} \times N_{timing}$$
每一项都是一个维度,乘起来就是状态空间。第一章还算了:一个简化 RPG 角色的理论状态空间大约是 10^60。这个数字大到穷举完全没有可能。
这跟普通软件的区别在哪?一个登录页面,你测”输入正确的用户名密码 → 登录成功”、再测”输入错误 → 提示错误”,两个用例覆盖了核心路径。一个游戏,两万个测试用例可能还没覆盖 1% 的状态空间。
二、实时性:对不是错,晚也是错
普通软件测的是”对不对”。游戏测的是”对不对,以及够不够快“。
60 FPS 意味着每帧不能超过 16.67ms。这 16.67ms 要完成输入处理 + 逻辑更新 + 物理计算 + 渲染 + 同步——任何一个环节拖后腿,帧就掉了。教程给了不同帧目标的细分:
| 游戏类型 | 目标帧时间 | FPS |
|---|---|---|
| 竞技游戏 | 8.33ms | 120 |
| 动作游戏 | 16.67ms | 60 |
| 休闲游戏 | 33.33ms | 30 |
但关键不在这张表,在于教程说的另一句话:帧率不稳定比低帧率更影响体验。从 60FPS 掉到 30FPS(50% 变化)比从 30FPS 降到 20FPS(33% 变化)更难受——人眼对帧率变化敏感,符合 Weber-Fechner 定律。
这让我想起 Go 游戏里 Tick 计时器那段的取舍——选了帧数当单位、放弃了真实时间,当时说的是”卡顿不会吃掉冷却”。放在这个上下文里看,Tick 方案其实就是在回避实时性的测试难题:如果用真实时间,你测冷却逻辑时还得同时测帧率波动下的表现,测试矩阵直接翻倍。
实时性另一个坑是竞态条件:逻辑线程和渲染线程分离,如果同步不当,视觉上会出现”角色瞬移”——不是逻辑算错了,是两个线程读到不同时刻的数据。这种 bug 很难复现——它依赖两个线程的精确执行时序,而时序每帧都略有不同。测 100 次可能只触发 3 次。
教程的常见陷阱列表里单独列了”异步系统的竞态测试”,并用一句话概括了核心困境:在开发环境里永远无法重现的生产环境 bug。
三、随机性:你不能”再跑一遍看看”
普通软件大部分场景是确定性的。登录失败,再试一次还是失败——确定性让 bug 可以复现。
游戏里充满随机:暴击、掉落、程序化地图、匹配对手。教程把游戏随机性分了四层:
1 | L1: 简单随机(掉落、暴击) → 二项检验 |
每一层需要不同的验证手段。L1 的暴击率,你不能”打 10 刀看看出了几次暴击”——教程给了置信区间公式:
$$\text{置信区间} = \hat{p} \pm z_{\alpha/2} \sqrt{\frac{\hat{p}(1-\hat{p})}{n}}$$
验证 10% 的暴击率,至少需要 10000 次攻击才能让置信区间落到 [9.4%, 10.6%]。100 次不够,1000 次勉强——统计验证需要大样本,而大样本意味着测试不能是手动的。
教程介绍了三种统计检验的用途分工:
- 二项检验:测一个概率对不对(暴击率 25% 对不对)
- 卡方检验:测多个类别分布对不对(掉落表 50/30/15/5 对不对)
- K-S 检验:测连续数值分布对不对(伤害浮动是不是真的均匀分布)
这三个检验我在读之前完全没概念。普通软件测试不会用到它们,因为普通软件很少有”25% 概率触发某行为”这种设计。
四、边界条件组合爆炸
这是第一章我印象最深的部分,因为它直接解释了为什么有些 bug 线上炸了,测试环境从来没出现过。
单个边界条件不难测。HP=0 和 HP=MAX,两个用例。
但边界条件会叠加。教程举的例子:
- 等级 1 + 最强装备(数值溢出)
- HP=1 + 吸血效果(除零风险)
- 坐标 Integer.MAX_VALUE + 移动(坐标回绕)
这些组合在正常测试流程里几乎不可能自然出现——玩家不会 1 级就拿到最强装备。但测试要做的恰恰就是找到这种现实中不太可能、但你必须保证别崩的极端叠加。
让我拿自己的 Go 游戏举例。这三个平时分散的参数:
| 参数 | 正常范围 | 危险的边界 |
|---|---|---|
score |
0 ~ 几千 | 接近 Int32 上限(21 亿) |
combo_multiplier |
1×~16× | 10 连以上,512× |
meteor_size |
1 / 3 / 5 | 大型陨石 = 5 分 |
打分公式很简单:
1 | score += meteor_size × combo_multiplier |
单独测,每个都安全:
- Combo=1,打一个大陨石:
score += 5✅ - Combo=10,打一个小陨石:
score += 512,从几千分开始加,距离溢出还远 ✅ - 没 combo 但分数很高:每次加 1~5 分,龟速增长,溢不出 ✅
三个边界叠在一起:
score = 2,147,483,640(离 Int32 上限差 7 分)
combo = 10 连(512×)
下一个陨石是大号(5 分)
1 | score += 5 × 512 |
如果代码里没有溢出检查,二进制绕回,分数变成负二十亿。玩家从”马上破纪录”变成”分数崩了”。
为什么正常测试抓不到:手动把 score 调到溢出边缘不会自然地出现在测试流程里。Combo 叠到 10 连你在单独测 combo 机制时跑过,分数涨到几千你在正常游戏测试里自然涨到了——但这两个边界永远不会在同一场测试里同时到达。N 个边界条件各有 M 个取值,理论上要测 M^N 个组合才能覆盖。教程给的解法是正交数组和风险优先级——这部分后面章节会展开。
五、一个开发者读完的感受
第一章不是在讲”怎么测游戏”,而是在讲”游戏为什么难测”。四个特点——交互复杂性、实时性、随机性、状态空间爆炸——每一个都在说同一件事:
普通软件的测试工作量是线性的,游戏测试的工作量是指数级的。
但这话反过来也成立:如果你能理解这四种难度的本质,你写的代码会天然更好测。
举个例子。写完 Go 游戏后我才意识到:Tick 计时器的选择、极坐标生成陨石、Combo 倍率的上限检查——这些设计决策,每一个都在无意中缩小了测试空间。当时我是因为”这样写更简单”做的选择,但换个角度看,我其实是在降低组合复杂度和实时依赖。
这是我读完第一章最大的收获:测试思维和开发思维在底层是相通的。 你不需要成为一个测试工程师才能受益于测试方法论——理解”什么东西难测”,本身就会改变你写代码的方式。
下一章:人工测试的艺术与科学——探索性测试策略、边界测试、Bug 复现技巧。




![[代码练习] 喝100杯奶茶](/img/HaveACup/cup.png)