游戏测试完全指南读书笔记(第一到三章)
《游戏测试完全指南》一共 22 章,这里把前三章的笔记合在一起:第一章是基础理论,讲游戏测试和普通测试差在哪;第二章讲人工测试的思维工具;第三章以 UE5 为例,讲调试后门在今天以什么形态存在。
第一章 游戏测试和普通测试到底差在哪
教程一共 22 章,第一章是基础理论。游戏测试和普通测试的差别,不只是“测的东西是游戏”。

一、交互复杂性:游戏的状态爆炸
普通软件的测试模型是清晰的:从输入到程序处理,再到输出 B。
但是,游戏的每一帧都在发生多系统的交叉作用。就比如说一个太空弹幕小游戏(**用 Go 做的小游戏**),一帧里同时跑的东西有:
1 | 帧 N: 玩家按空格 → 射击冷却检查 → 生成子弹 → 注册碰撞事件 |
复杂的网状交互让游戏的状态爆炸式增长。教程里给了个 MMO 战斗的复杂度公式:
C_combat = N_skills × N_buffs × N_targets × N_positions × N_timing
每一项都是一个维度,乘起来就是状态空间。计算出一个简化 RPG 角色的理论状态空间是一个天文数字,大到穷举完全没有可能。
二、实时性:细节到帧
游戏对响应时间要求很严格,必须考虑时间轴上的表现是否符合预期。
一个 60 FPS 的游戏意味着每帧不能超过 16.67ms。这 16.67ms 要完成输入处理 + 逻辑更新 + 物理计算 + 渲染 + 同步。不同的游戏类型对于游戏帧数的要求也不相同,教程给了不同帧目标的细分:
| 游戏类型 | 目标帧时间 | FPS |
|---|---|---|
| 竞技游戏 | 8.33ms | 120 |
| 动作游戏 | 16.67ms | 60 |
| 休闲游戏 | 33.33ms | 30 |
电影行业里 24 帧就可以让人感到流畅,然而,24 帧在一些需要主动操作的游戏中却让人感到明显卡顿。一个原因是,游戏的逻辑都是逐帧处理的,并且每一帧都是一个独立的图像,与相邻帧之间没有转换关系,所以当玩家进行操作时,很容易观察到画面的跳变。所以对竞技游戏来说,120 帧已经成为标配。
帧率不稳定比低帧率更影响体验。根据 Weber-Fechner 定律,人眼对帧率变化非常敏感。所以在性能测试中要看 99% 帧率(也叫 1% low):99% 的帧率都要高于某个标准。比如 5090 显卡最高画质下,要求游戏 99% 的帧率大于 120 帧。
实时性另一个坑是竞态条件:
这个虽然在软件里也会出现,但通常是在数据处理时才会特别关注,而游戏由于每一帧都要计算,所以这种问题非常普遍。逻辑线程和渲染线程分离,如果同步不当,客户端视觉上会出现”各种奇怪瞬移”,也会出现服务器端计算出错误的结果。
两个线程读到不同时刻的数据。这种 bug 很难复现,它依赖两个线程的精确执行时序,而时序每帧都略有不同。测 100 次可能只触发少数几次。
还有一种是多线程状态抢占:玩家同时拾取同一物品,交易过程中断线,多个服务同时修改玩家数据。
教程的常见陷阱列表里单独列了”异步系统的竞态测试”,概括下来就是:在开发环境里永远无法重现的生产环境 bug。
三、随机性:复现困难
普通软件大部分场景是确定性的。登录失败,再试一次还是失败,确定性让 bug 可以复现。
游戏里充满随机:暴击、掉落、程序化地图、匹配对手。教程把游戏随机性分了四层:
1 | L1: 简单随机(掉落、暴击) → 二项检验 |
每一层需要不同的验证手段。L1 的暴击率不可能测无限次,只能统计足够多的样本,看是否符合概率。
验证 10% 的暴击率,至少需要 10000 次攻击才能让置信区间落到 [9.4%, 10.6%]。在足够的数据下才能让大数定律发挥作用。
三种统计检验的用途分工:
- 二项检验:测一个概率 (25%暴击率)
- 卡方检验:测多个类别概率(奖励掉落表 50/30/15/5 )
- K-S 检验:测连续数值分布(伤害浮动是不是真的均匀分布)
这三个检验我在读之前完全没概念。普通软件测试不会用到它们,因为普通软件很少有”25% 概率触发某行为”这种设计。
四、边界条件组合爆炸
单个边界条件不难测。HP=0 和 HP=MAX。
但边界条件会叠加。教程举的例子:
- 等级 1 + 最强装备(数值溢出)
- HP=1 + 吸血效果(除零风险)
- 坐标 Integer.MAX_VALUE + 移动(坐标回绕)
这些组合在正常测试流程里几乎不可能自然出现。但测试要找到这种现实中不太可能,但必须保证的极端叠加。
实例分析
等价类划分
练习1.1 120个英雄,4个技能如果要测试所有英雄的所有技能等级组合,需要多少个测试用例?如果采用等价类划分,将技能等级分为低(1-2)、中(3-4)、高(5)三类,测试用例可以减少到多少?
完整测试用例数:120 × 5^4 = 75,000 个。
采用等价类后:120 × 3^4 = 9,720 个。减少率 87%。
等价类的划分依据必须来自对游戏机制的深入理解。某些技能升级只改变数值不改变机制,才能将其归为一类。如果升级改变了技能形态(比如火球变火雨),就必须独立测试。
概率测试
练习1.2 游戏的暴击率标称为 25%。测试 1000 次攻击,暴击了 280 次。请计算 95% 置信区间,并判断是否符合预期。
计算过程省略,方法可以搜索二项检验。
计算结果为 [0.2522, 0.3078]
标称值 25%(0.25)落在置信区间下限 0.2522 之外。这个暴击率系统大概率有问题,实际暴击率偏高。
关键在于两点:一是样本量要够大,大数定律才能起作用;二是统计显著性和实际显著性要分开看。280 次比 250 次多了 30 次,在 1000 次测试里是 3% 的偏差,统计学上显著;但 3% 的差异在实际中很小,最好用更大样本量再测一次。
测试金字塔设计
练习1.3 设计一个测试金字塔,针对一款包含PvE和PvP的卡牌游戏。请为每一层列出3个具体的测试项目。
1 | /\ |
- 单元测试(底层):单卡效果解析、伤害计算公式、状态机转换
- API/逻辑层:回合流程控制、卡牌抽换机制、战斗结算引擎
- 集成测试:PvE 关卡AI行为交互、PvP匹配对战流程、卡组构建保存/读取
- 探索性测试(顶层):新卡对 Meta 的冲击、极端卡组组合、UI/UX体验流畅度
越往上测试成本越高,发现的问题影响范围也越大。
技能树测试策略
练习1.4 某MMORPG的技能系统:30个主动技能,每个10级;存在前置关系(学B需要A达到5级);同时最多激活8个技能。请设计测试策略。
这个题的关键在于:**寻找高危”有效组合”**。
测试策略分两步:
等价类降维:10级技能分成低(1-3)/中(4-7)/高(8-10),每个区间内数值变化量很小。激活槽位 8 个,但玩家的真实选择集中在 3-5 种流派(爆发/持续/控制),按流派聚类。
高风险组合:教程列了 7 个常见陷阱,这里最相关的是边界叠加:
- 全技能满级 + 同时激活 = 数值是否溢出
- 洗点/重置 → 重新加点 = 前置关系是否残留
核心思路:用流派聚类覆盖真实玩家行为。教程参考答案给出的方案是约 130 个核心用例覆盖 80% 以上场景。
技能依赖链简单估算测试:30/2 = 15 用例
孤立测试:30 技能 × 3 类
组合测试:寻找 3 种流派(爆发/持续/控制)
集成测试:5种场景
冗余用例:10种
总测试用例数量:15 + 30 × 3 + 3 × 5 + 10 = 130
Roguelike地牢验证
练习1.5 验证程序化生成的Roguelike地牢:(a)所有地牢连通;(b)难度分布合理;(c)宝藏分布公平。
(a) 连通性——图论解决
把房间当节点,走廊当边,地牢就是一张无向图。判断连通性就是判断图是否全连通:
此外,与其生成后检测、不通就重试或修复,不如在生成算法里加连通性断言。生成每个走廊时检查是否连接了两个不同连通分量,保证生成完成时必然连通。这是”测试左移”的体现。
(b) 难度分布——统计检验
定义难度 = 敌人总战力 / 玩家可获得资源。生成 1000 个地牢,画难度分布直方图。期望是近似正态分布,不会出现极度简单(裸奔通关)或极度困难(必死局)。教程参考答案使用 Shapiro-Wilk 检验正态性,配合 3σ 原则检查极端值。
(c) 宝藏公平性——路径分析
公平 ≠ 均匀。公平是指:任何起点到任何终点的路径上,可获得的关键资源(钥匙、武器)数量差异不超过阈值。教程参考答案使用了三种空间统计方法:最近邻分析(判断是否随机分布)、象限卡方检验(判断是否均匀分布)、可达性分析(路径长度变异系数 < 0.3)。
这三个子问题分别用到了图论、统计学、路径分析,没有传统的”点点点测法”。
帧数据精确测量
练习1.6 格斗游戏运行在60FPS,每个招式有启动帧、活动帧、恢复帧。如何自动化测试所有角色的所有招式的帧数据准确性?
教程参考答案的核心思路:帧步进模式 + 状态机监控。
- 锁定 60FPS,关闭垂直同步,使用确定性输入系统(帧级输入队列)
- 逐帧记录角色状态和碰撞盒:
T0 输入指令 → T0+n 检测攻击判定激活(启动帧=n)→ T0+n+m 攻击消失(活动帧=m)→ T0+n+m+k 恢复待机(恢复帧=k) - 验证公式:总帧数 = 启动 + 活动 + 恢复,连招规则:A动作的恢复帧 + B连招的启动帧 < 对手硬直
- 批量矩阵:20角色 × 30招式 × 4种情况(空击/命中/防御/反击)= 2400用例
不可能手工逐帧测量 2400 个用例,必须自动化批量验证。
LOD系统:视觉与性能的双重验证
练习1.7 开放世界游戏的LOD系统,如何验证LOD切换不造成视觉跳变,同时确保性能优化有效?
解法是用两条独立的验证轨道:
| 轨道 | 指标 | 方法 | 通过标准 |
|---|---|---|---|
| 客观性能 | 三角形减少率、DrawCall减少率、FPS提升 | 自动化脚本在不同场景下采集 | 正常场景三角形减少60%+ |
| 客观视觉 | SSIM结构相似性、像素差异率、轮廓偏移 | 逐帧截屏对比 | SSIM > 0.95 |
| 主观视觉 | A/B盲测评分 | 录制切换视频,随机播放给测试者 | 平均分 > 3.5/5 |
这揭示了游戏测试的一条原则:能自动化的用数据说话,自动化不了的用盲测说话。两条轨道互为补充,缺一不可。
网络延迟补偿:把不确定性矩阵化
练习1.8 实时PvP手游使用客户端预测+服务器回滚。如何测试在不同网络条件下游戏体验仍可接受?
教程参考答案展示了一个完整的系统化方法:先把不确定性变成确定性矩阵,再逐格验证。
1 | 网络测试矩阵:3 × 3 × 3 = 27 种环境 |
然后对每种环境定义可量化的体验指标:
- 预测准确性:20ms延迟下预测准确率 > 95%,200ms下 > 75%
- 响应性:输入到视觉反馈延迟(含预测)< 50ms
- 公平性:”被击中但已躲开” < 每分钟1次
- 压力场景:多人团战 + 每秒10+技能 + WiFi切4G + 高APM操作
核心思路:把网络测试变成确定性条件。用网络模拟器,在固定条件下对每种环境做统计验证。
六、八道练习题的方法论提炼
方法论一:降维,压缩指数级空间到可测范围
第一个问题:状态空间指数爆炸,穷举没有可能。
八道题里,练习1.1和1.4给出了两个降维手段:
- 等价类划分(1.1):必须理解游戏机制(哪些升级只改数值、哪些改了行为)
- 流派聚类(1.4):真实玩家只玩 3-5 种流派。用玩家行为数据聚类,用约束求解覆盖边界。
两条原则:
- 降维的粒度取决于对游戏机制的理解深度
- 降维的方向要沿着真实玩家行为走,而非均匀抽样
方法论二:量化,用数据替代描述
第二个问题:必须把主观体验翻译成可测量的数字。玩家的”打击感””流畅感””概率抱怨””掉帧”这些模糊描述,都应该在测试里变成可量化语言。
| 练习 | 验证目标 | 量化手段 |
|---|---|---|
| 1.2 | 暴击率是否准确 | 置信区间 |
| 1.5b | 地牢难度是否合理 | Shapiro-Wilk |
| 1.6 | 帧数据是否精确 | 帧级状态机 + 公式计算验证 |
| 1.7 | LOD是否平滑 | SSIM + 像素差异率 + A/B盲测 |
| 1.8 | 网络体验是否可接受 | 客户端预测准确率 + 回滚深度 + 延迟预算 |
这些工具分两类:客观量化(置信区间、SSIM、帧时间)和主观量化(A/B盲测评分、玩家投诉率)。两者都要有。
方法论三:分层,不同风险层级用不同武器
成本-收益的最优分配。
1 | 测试金字塔越底层 → 越确定、便宜、适合自动化 |
| 金字塔层级 | 对应练习 | 验证手段 |
|---|---|---|
| 单元/API | 1.1, 1.4, 1.6 | 等价类、拓扑验证、帧步进自动化 |
| 集成 | 1.2, 1.5a, 1.5c, 1.8 | 统计检验、图论连通、路径分析、网络矩阵 |
| 系统/探索 | 1.3(设计层), 1.7(视觉), 1.8(体验) | A/B盲测、玩家行为数据、体验评分 |
不要在应该自动化的层级做手工测试(如逐帧数帧数据),也不要在应该人工判断的层级试图写脚本(如判断”手感好不好”)。应该在正确的层级使用正确的武器。
方法论四:左移——测试越早介入,成本越低
- 地牢连通性验证,可以生成时就断言——把测试逻辑嵌入生成算法
- 帧数据验证,可以CI 每次构建自动跑用例
左移就是让测试从开发阶段就开始介入,提前预防 bug 堆积。
结语
八道练习做下来,方法其实就三类:降维、量化、分层,外加一个左移。够用了。
第二章 游戏人工测试的思维工具
教程第二章讲人工测试。
人工测试不只是”点点点”,有一整套测试思维工具。

一、探索性测试:有限时间内的创造性测试
专业的探索性测试有严格的框架,叫会话式测试。
每次测试都是 45-90 分钟的时间盒,围绕一个”测试宪章”展开。
第一章讲过,游戏状态爆炸,90% 用例覆盖不可能。单元测试和自动化测试之后,剩下的高危场景只能靠经验和直觉去探索——从玩家的视角出发,找出潜在 bug。
测试宪章是一种任务声明,比如,”在未来 60 分钟内,将探索商城系统的支付边界,重点关注货币换算和余额不足场景”。
宪章要满足 SMART 原则:具体、可衡量、可实现、相关、有时限。
宪章最重要的作用是量化测试工作,同时提醒自己主线任务。
时间盒也不要求将所有东西都测完,只要求在规定时间内找到足够多的问题。
为什么越测发现的新问题越少
一个覆盖率增长模型,用微分方程描述:
1 | dC/dt = k · (S - C) · exp(-αt) |
其中 C 是已覆盖的功能,S 是总功能,k 是学习速率,α 是疲劳系数。
这个公式翻译成人话就是:刚开始测的时候,对系统不熟,发现新问题的速度很快。随着测试进行,剩余未测功能变少了,人也累了。连测两小时后效率断崖式下降。所以要写测试宪章,有预算时间的测试更有效。
90 分钟分配方案:
| 阶段 | 时长 | 做什么 |
|---|---|---|
| 准备 | 5 分钟 | 阅读宪章、回顾上次测试的笔记 |
| 探索 | 70 分钟 | 执行测试,每 30 分钟停下来记录一次 |
| 整理 | 10 分钟 | 把发现的 bug 整理成可提交的报告 |
| 总结 | 5 分钟 | 记录覆盖率变化、标记下一步要测的区域 |
怎么判断测试是否高效
一个指标:信息密度 D。
1 | D = (新发现的问题数 + 新覆盖的功能数) / 测试时间(分钟) |
优秀测试员的 D 值大于 0.5。也就是说,每两分钟至少要产出一个有价值的发现。
如果两分钟过去了你什么都没发现,说明要么测试策略有问题,要么该换一个宪章了。
两个启发式检查清单
人工测试没法遍历所有情况,但可以用启发式模型帮助系统化地扫描。两个维度:
SFDIPOT(面向通用软件):
| 维度 | 含义 | 游戏测试中的例子 |
|---|---|---|
| Structure | 结构 | 代码模块、资源文件、配置表 |
| Function | 功能 | 攻击、购买、存档、匹配 |
| Data | 数据 | 玩家属性、道具数据、排行榜分数 |
| Interface | 界面 | UI 布局、HUD、菜单导航 |
| Platform | 平台 | 不同设备、操作系统、屏幕分辨率 |
| Operations | 操作 | 安装、更新、断线重连、后台切换 |
| Time | 时间 | 帧率、延迟、冷却时间、定时事件 |
GAMEPLAY(面向游戏专有):
| 维度 | 含义 |
|---|---|
| Goals | 目标:任务、成就、胜利条件 |
| Actions | 动作:攻击、移动、交互、使用道具 |
| Mechanics | 机制:伤害公式、碰撞检测、AI 行为 |
| Economy | 经济:金币、钻石、经验值、掉落率 |
| Progression | 进度:等级、技能树、章节推进 |
| Loop | 循环:核心循环、奖励周期、每日任务 |
| Aesthetics | 审美:画面、音效、UI 风格 |
| Yield | 产出:玩家获得什么、反馈是否及时 |
这两个清单的作用:不知道该测什么的时候扫一眼,看有没有维度被忽略。是很好用的防遗漏工具。
通用测试启发式
| 维度 | 含义 |
|---|---|
| Frequent | 频繁:高频操作路径、热点功能、常用组合 |
| Error | 错误:错误处理机制、异常恢复、错误提示 |
| Worst-case | 最坏情况:极限条件、资源耗尽、网络中断 |
| Happy path | 正常路径:期望流程、新手引导、主线任务 |
| Interruption | 中断:异常中断处理、断线重连、暂停恢复 |
| Combination | 组合:功能交互、状态叠加、并发操作 |
| Configuration | 配置:不同设置组合、分辨率适配、语言切换 |
| Usability | 可用性:用户体验、操作便利性、信息可读性 |
| Performance | 性能:响应时间、帧率稳定性、内存占用 |
| Platform | 平台:跨平台兼容、设备差异、系统版本 |
| Scalability | 可扩展性:负载能力、并发上限、数据规模 |
二、边界测试:找到那个”刚好会炸”的点
第一章讲了边界条件组合爆炸,这章进入了实战层面边界具体在哪。
显式边界 与 隐式边界
显式边界就是文档里写清楚的,整数溢出(2^31 - 1 是 32 位有符号整数的上限,2^63 - 1 对应 64 位)、配置表里标好的等级上限、业务逻辑里定义的 VIP 等级——这些都有明确的数字。
此外,还有一种棘手的隐式边界,游戏开发者自己都可能不知道,比如:
- 渲染管线一次最多绘制多少个对象?超过之后的表现为闪烁还是崩溃?
- 碰撞检测的精度阈值是多少?两个移动速度极快的物体会不会”穿模”?
- 网络包大小有没有隐式上限?某些特殊操作组合产生的数据包超限后会怎么处理?
- 寻路网格的节点数有没有上限?超大开放世界地图会不会让寻路算法超时?
这些问题正常流程很难碰到,复现也困难,非常依赖知识积累和直觉。
边界测试点的通用集合:
1 | B = { a-ε, a, a+ε, (a+b)/2, b-ε, b, b+ε } |
其中 a 和 b 是边界值,ε 是一个非常小的偏移量。以等级系统 1-100 级为例,测试点就是 0 级、1 级、99 级、100 级、101 级,再加上中间一个正常值(比如 50 级)。这七个点覆盖了”边界外、刚好边界、边界内、中间值”四种情况。
浮点数的陷阱
游戏里浮点数无处不在:坐标、速度、伤害系数、时间。
一个经典例子:16777216.0f + 1.0f == 16777216.0f。
32 位浮点数的尾数只有 23 位,小于 16777216 的时候步长是 1,一旦超过这个值,步长变成 2。也就是说,加 1,结果根本没变。这在伤害计算里意味着什么?攻击力够高的时候,加一点攻击力可能完全没有效果。
还有 NaN(不是一个有效数字)和次正规数(极小值区间的精度异常)。这些边缘值在任何计算中出现都会像病毒一样传播,让所有后续结果都变成 NaN。
状态机的三级分层
游戏的状态机比普通软件复杂得多,分了三级:
1 | 宏观层:菜单 → 游戏中 → 暂停 → 结算 |
每一层都是一个独立的状态机,而且它们是嵌套的。玩家可能在”战斗中”的细节层,同时处于”攻击”的原子状态,然后突然接了个电话,App 进入后台(宏观层切换),回来后所有状态需要正确恢复。
测试策略是分四步递进的:
- 状态覆盖:保证每个状态都被访问过
- 转换覆盖:用转移矩阵 T_ij 记录每两个状态间是否可能出现合法转换,逐一验证
- 路径覆盖:挑几条高频路径(比如”登录→匹配→战斗→结算→返回大厅”)做端到端验证
- 条件覆盖:同一个转换在不同条件下行为不同(比如”战斗→死亡”,HP=1 时扣 1 点和 HP=100 时扣 100 点结果一样,但中间状态不同)
大多数团队能做到转换覆盖就不错了。
并发状态机的问题更困难。如果有 n 个独立状态机并行运行(战斗状态机、背包状态机、网络状态机……),理论组合数是它们状态数的乘积。建议是用配对测试降维。不测所有组合,只保证每对状态被覆盖至少一次。
三、玩家行为建模
这一节讲如何从玩家视角测试。
Bartle 四型
引用了经典的 Bartle 玩家分类:
| 类型 | 驱动力 | 典型行为 | 测试关注点 |
|---|---|---|---|
| 成就型 | 变强、通关 | 冲排行榜、刷成就 | 数值上限、排名系统 |
| 探索型 | 发现、理解 | 找彩蛋、研究机制 | 隐藏内容、边界交互 |
| 社交型 | 连接、影响 | 聊天、公会、交易 | 社交功能压力、消息系统 |
| 杀手型 | 竞争、支配 | PvP、捣乱 | 平衡性、反作弊、恶意行为 |
每个类型的玩家会以完全不同的方式玩游戏。成就型玩家会压着数值边界走,探索型玩家会触发一些从没想过的操作组合,杀手型玩家会主动寻找能伤害其他玩家体验的手段。测试时要为每种类型设计不同的测试宪章。
用信息熵识别机器人
用马尔可夫链建模玩家行为,然后计算信息熵来判断操作模式。
假设我们把玩家操作分类(攻击、移动、使用道具、等待),然后统计相邻操作之间的转移概率。正常的玩家操作序列有中等程度的熵:既不是完全随机,也不是完全可预测。
1 | 信息熵 H(X) = -Σ p(x_i) × log₂(p(x_i)) |
低熵意味着行为高度规律,转移概率集中,很可能是自动化脚本。高熵意味着每个操作的选择近乎等概率,不像真人——真人不会在”攻击”和”打开背包”之间以 50/50 随机选择。
练习 2.3 给了一个 12 步操作序列的例子:5 种操作分别出现 2/4/2/2/2 次,计算出熵值约 2.25 bits。等概率分布的最大熵是 log2 5 ≈ 2.32,2.25 已经很接近它——操作过于均匀,不像真人。
漏斗分析和断点检测
两种分析玩家群体的方法。
漏斗分析:追踪玩家在每个阶段的留存率。数据可能是:
1 | 新手教程 100% |
每一层流失率突然变高的地方,就是测试需要重点关注的区域。如果”首次失败”到”首次付费”的转化率异常低,可能是因为失败后的引导不够,或者付费入口在失败界面没展示好。
断点检测:难度曲线的二阶导数。如果 d²D/dt²(难度变化的加速度)超过阈值 θ,说明难度在某个点突然陡增。这不是”难不难”的问题,而是”难度增长是否平滑”的问题。
四、Bug 复现
Bug 复现是整个测试流程里最考验思维能力的环节。
Delta 调试:二分法找最小复现集
核心问题是:**”触发这个 bug 的最小操作集合是什么?”**
假设你有一个 20 步的操作序列能稳定复现 bug,但不是每一步都必要。Delta 调试的做法是二分法递归:
- 把 20 步对半分,先测前 10 步——能复现吗?能的话问题在前半段,丢弃后 10 步
- 把剩下 10 步再对半分,测前 5 步……
- 直到找到”去掉任何一步 bug 就不出现”的最小集合
复杂度在 O(n log n) 到 O(n²) 之间,取决于 bug 的”分布特征”。教程练习 2.5 给出了一个 20 步的序列,经过四轮二分,最终锁定最小复现集合为 [H, I, K, L, M] 五步。
因果链追踪
理解bug为什么发生。可以使用因果链模型:
1 | 触发条件 → 状态变化 → 错误传播 → 可见症状 |
看到的 bug 是”可见症状”,比如角色突然飞天。
但真正的根因可能在几层之前:某次状态切换时一个标志位没重置,然后这个错误状态被正常逻辑处理了,最后才在渲染层表现为异常坐标。
分层日志在这里是关键工具:
| 级别 | 查什么 |
|---|---|
| FATAL | 系统崩溃的直接原因 |
| ERROR | 异常被捕获的位置 |
| WARNING | 被降级处理的边缘情况 |
| INFO | 正常流程的节点记录 |
| DEBUG | 变量的中间值 |
| TRACE | 每一帧的精确状态 |
追踪 bug 时,从 ERROR 层入手,向下钻到 DEBUG/TRACE 去重建因果链。
确定性重放
“刚才还能复现,现在又不出现了”。
应对这种问题引入一个新概念:游戏状态是初始状态和所有输入的函数。
1 | S_n = f(S_0, I_1, I_2, ..., I_n) |
如果你能记录 S_0(初始状态:关卡、装备、属性值)和所有输入序列 I,理论上就能在任何时候完美重放。很多游戏引擎已经内置了重放系统(回放录像文件用的就是这个原理),但测试环境往往要求更精细(包括随机种子的记录、网络包的时序等)。
教程列举了 8 个常见的复现陷阱:
- 只在特定帧率下触发(帧率变化后消失)
- 依赖内存中未初始化的值(重启后消失)
- 竞态条件(只在特定线程调度下触发)
- 缓存污染(清缓存后消失)
- 浮点精度(只在特定硬件上触发)
- 时间相关(只在服务器特定时间触发)
- 账号状态残留(新建账号不可复现)
- 多设备差异(只在特定配置上出现)
每个陷阱对应的调试技巧也不一样:二分法缩小范围、时光机调试(用保存的快照回退到关键帧)、差异对比(能复现和不能复现的环境到底差在哪)、最小化重现、假设验证(先猜一个根因再设计实验验证)。
五、实例分析
练习 2.1:探索性测试宪章设计
练习 2.1 探索性测试宪章设计:为一个 MMORPG 的交易系统设计三个不同焦点的测试宪章,每个宪章时长 45 分钟。
任务:验证装备系统基本功能流程
目标:
- 验证装备穿戴/卸下的基本功能
- 确认装备堆叠的上下限处理
- 检查多装备buff叠加的数值计算
范围:- 穿戴、卸下、替换装备
- 装备堆叠(0/1/上限/上限+1)
- 2 件以上装备特效同时生效
约束:45 分钟
预期风险:
- 快速连续替换可能导致数值残留
- 堆叠数 = 0 时装备可能不消失
- 装备buff产出冲突或者链式反应
任务:验证装备系统安全性流程
目标:
- 异常断线后装备状态回滚
- 背包占满时装备拾取
- 两个客户端同时操作同一件装备
范围:网络重连,竞态条件操作装备
约束:45 分钟
预期风险:回滚导致装备异常变化,背包溢出装备消失,客户端同时抢占装备导致装备复制,
任务:验证装备系统性能
目标:
- 大量特效叠加对性能影响
- 高频装备掉落/拾取
范围:帧率 ≥ 60fps,LOD0 三角面 ≥ 10 万时不掉帧,特效表现正常,一帧拾取 10 件装备延迟 ≤ 50ms
约束:45 分钟
预期风险:跳变,帧率掉60,丢包,事件被回滚。
练习 2.2:等级系统的边界值分析
练习 2.2 某 RPG 游戏等级系统 1-100 级,经验公式 E(n) = 100n²。请设计边界测试,包括等级边界和经验值边界。
等级边界可以用七点法:
1 | 等级测试点:0(除0错误), 1, 50, 99, 100, 101 |
练习 2.3:从操作序列中找出机器人
练习 2.3 一段 12 步操作序列中,5 种操作分别出现 2/4/2/2/2 次。计算信息熵,判断是否是机器人在操作。
计算过程:
1 | 各操作概率:p1 = 1/6, p2 = 1/3, p3 = 1/6, p4 = 1/6,p5 = 1/6 |
5 种操作等概率分布时,最大的信息熵是 log2 5 = 2.32。熵率 = 2.25/2.32 ≈ 0.97,接近 1。这意味着操作过于均匀,人类的操作更像长尾分布。疑似自动化脚本。
正常玩家的操作频率分布通常接近幂律(少数操作高频,多数操作低频),出现极端均匀分布,就要怀疑是 bot。
练习 2.4:战斗状态机的全覆盖
练习 2.4 某格斗游戏有 6 种战斗状态:待机、攻击、受击、闪避、技能、死亡。设计状态转换测试,需要的测试用例数。
6 个状态,两两之间可能存在转换(包括自身转换,比如”技能→技能”表示连招)。理论转移总数是 6×6 = 36 条,但不是每条都有意义(比如”死亡→攻击”不存在)。
粗略统计有效转换约 25-30 条。算上条件分支(攻击衔接窗口、技能冷却中、体力不足等),建议设计 25-30 个核心用例,覆盖:全部状态至少一次 + 全部有效转换 + 3-5 条高频路径的端到端验证。
练习 2.5:Delta 调试的实战演练
练习 2.5 以下 20 步操作能复现一个 bug,用 Delta 调试找出最小复现子集。
1 |
|
Delta 调试流程:
第 1 轮:测试前 10 步 [A-J]。假设不能复现,那么问题在后 10 步。
第 2 轮:把后 10 步 [K-T] 再二分,加前 5 步组成 [A-E, K-O]。假设不能复现,那么问题在 [P-T]。
第 3 轮:测试 [A-E, K-O, P-R] vs [A-E, K-O, S-T]。锁定到 [A-E, K-O, P-R]。
第 4 轮:在有问题的子集里继续收缩。最终锁定最小复现集合 [H, I, K, L, M]。
不需要理解 bug 的原理就能找到复现集。它纯粹是算法化的排除法,但前提是已经有一个能稳定复现的”大集合”。
如果连稳定复现都做不到,那就得先回到因果链追踪,找到触发条件。
练习 2.8:风险评估矩阵
练习 2.8 对一个包含商城系统的 MMORPG,进行风险评估。要求按”概率 × 影响度”矩阵打分,并提出资源分配建议。
风险评估矩阵:
| 风险场景 | 概率 | 影响度 | 风险值 |
|---|---|---|---|
| 支付流程金额错误 | 中 | 极高 | 高 |
| 道具复制 bug | 低 | 极高 | 中高 |
| 排行榜数据异常 | 中 | 中 | 中 |
| UI 显示错误(不影响功能) | 高 | 低 | 中低 |
| 字体渲染问题 | 高 | 极低 | 低 |
| 极端并发下服务器崩溃 | 低 | 高 | 中 |
建议是:支付系统(高概率×高影响)应该吃掉 40% 的测试资源。
道具复制 bug 虽然概率低,一旦出现影响无法挽回的,也应该分配约 20% 的资源做专项测试。
风险应该决定测试资源的分配优先级。
六、四个方法论提炼
方法论一:时间盒思维
Bug无穷无尽。时间盒思维要求在有限时间内产出最具决策价值的信息,放弃追求完美覆盖。
具体做法:
- 每次测试前明确”宪章”,知道 45 分钟要回答什么问题
- 复盘总结时可以用信息密度 D 判断效率,低于 0.5 就调整策略
方法论二:培养对临界点的直觉
可以按下面的问题自查:
- 比最小值小 1 会怎样?
- 比最大值大 1 会怎样?
- 中间有没有隐式边界(浮点精度、数据结构限制、网络包大小)?
方法论三:bug链溯源
出现Bug,回溯它的因果链。
1 |
|
每一步都需要”假设→验证”的循环。
Delta 调试、分层日志、确定性重放,都是这个思维过程的工具。
方法论四:玩家视角
成就型玩家、探索型玩家、杀手型玩家会用完全不同的方式拆解游戏。
具体做法:为每种 Bartle 类型设计独立的测试宪章。测经济系统时,想象你是一个成就型玩家,你会最大化收益、找漏洞、研究最优路线。同一套系统,再想象你是一个杀手型玩家,你会找不给钱的途径、恶意交易、利用经济系统攻击其他玩家。
结语
这一章的所有工具都在回答同一个问题:有限时间里,把注意力放在哪里。
第三章 从作弊码到UE5调试后门
教程第三章讲作弊码与调试后门。这一章有时代局限:现在游戏引擎已经内置了调试后门,不需要自己手搓作弊码。
所以这一章不照搬原文。以 UE5 为参照,只挑今天仍然有用的概念,看它们在 UE5 里以什么形态存在、怎么用。

一、CheatManager:开发者控制面板
传统作弊码消失了
原文花了很大篇幅讲 KONAMI CODE 的有限状态机实现、输入序列哈希化、动态生成作弊码。这些东西在 2026 年几乎没人在用了,除非你要手搓游戏引擎。

UE5 里的 CheatManager
UE5 作弊系统的架构:APlayerController 持有一个 UCheatManager 实例,任何标记为作弊的命令都通过它路由:
1 | // 在 APlayerController 中: |
内置作弊函数包括:
| 命令 | 对应函数 | 做什么 |
|---|---|---|
god |
CheatManager::God() |
无敌 |
fly / walk / ghost |
CheatManager::Fly() etc. |
飞行/穿墙/幽灵模式 |
teleport |
CheatManager::Teleport() |
瞬间传送 |
slomo N |
CheatManager::Slomo(N) |
时间缩放,slomo 0.5 即半速 |
playersonly |
CheatManager::PlayersOnly() |
冻结所有非玩家 Actor |
freezeframe N |
CheatManager::FreezeFrame(N) |
冻结渲染 N 秒 |
viewself / viewbot |
切换视角 | 观察 AI 行为 |
changeSize N |
改变碰撞体积 | 测试碰撞边界 |
如果项目需要自定义作弊能力,继承 UCheatManager 并写 UFUNCTION(Exec) 函数即可:
1 | UCLASS() |
然后在 APlayerController 中指定 CheatClass = UMyCheatManager::StaticClass()。
这意味着可以合理利用 Cheat 命令:写一个 CheatManager 函数,在控制台敲几行命令,就能进入想要的测试状态。
二、控制台系统
UE5 的控制台架构
UE5 的控制台底层是 IConsoleManager 管理的全局命令注册表。任何 CPP 模块都可以在静态初始化阶段注册命令和变量,运行时通过字符串查找。
1 | 键盘输入 |
命令在编译期注册,运行时只做字符串查找。所以写了新的命令记得重新编译。
UFUNCTION(Exec):把任意函数变成控制台命令
除了通过 IConsoleManager 注册,UE5 还有一种更简单的途径。在任何 UObject 派生类(特别是 APlayerController、AHUD、UCheatManager、AGameMode)上标记 UFUNCTION(Exec):
1 | UFUNCTION(Exec) |
引擎会自动把这个函数注册为控制台命令 ResetAllCooldowns,参数自动解析。不需要手动调用 IConsoleManager::Get().RegisterConsoleCommand()。
你可以在不修改测试框架的情况下,通过控制台直接调用任何标记了 Exec 的函数。
这样一个 Cheat 命令就构建完成了,可以用控制台命令来驱动测试流程。
管道和脚本化
原文练习 3.6 设计了一个支持 |、>、$var 的控制台解析器。UE5 的控制台不支持管道,但你可以通过 -ExecCmds 启动参数批量执行命令:
1 | MyGame.exe -game -log -ExecCmds="open TestLevel, god, spawn_enemy 10, stat fps" |
-ExecCmds 里的逗号分隔的命令会依次执行。
三、CVar
如果你想临时打开某个调试显示(比如碰撞框),看完再关掉。
那适合用 CVar,这是一个在控制台可以随时使用的开关。
TAutoConsoleVariable:一行注册
1 | TAutoConsoleVariable<bool> CVarShowCollision( |
任何地方都可以读它的值:
1 | bool bShowCollision = CVarShowCollision.GetValueOnGameThread(); |
ECVF 标志位
UE5 的 ECVF_ 类似于标记作用:
| Flag | 含义 | 行为 |
|---|---|---|
ECVF_Cheat |
作弊命令 | Shipping 构建中不可设置,不可读取 |
ECVF_ReadOnly |
只读 | 运行时无法通过控制台修改 |
ECVF_Scalability |
画质可拓展 | 随 Scalability 组一起变化 |
ECVF_Default |
默认 | 无特殊限制 |
ECVF_SetByConsole |
来源追踪 | 标记此值是通过控制台设置的(非代码或 Scalability) |
ECVF_Cheat 特别值得关注:在 Shipping 配置下,引擎不仅拒绝修改——它连读取都会返回默认值。带这个标记的变量,在正式发布的游戏里完全不存在。
OnChangedDelegate:值变化即事件
每个 IConsoleVariable 都支持注册回调:
1 | CVarShowCollision->OnChangedDelegate().AddLambda([](IConsoleVariable* Var) |
测试中的实际价值:可以监听关键 CVar 的变化,作为自动化测试的”断言点”。比如监听 g.GodMode,从 0 变 1 时自动打日志标记”测试环境开始”,从 1 变 0 时做状态一致性检查。
Scalability Groups:预设系统
UE5 的 Scalability 系统其实就是原文说的”CVar 预设”。把一批 CVar 按档位(Low/Medium/High/Epic)打包,通过 sg. 前缀控制:
1 | sg.ViewDistanceQuality 0 // 最低 |
每个 sg. 命令背后是几十个渲染 CVar 同时变化。测试中的用法:一键切到最低画质跑一轮,切到最高画质再跑一轮,验证渲染不会因为某个 CVar 组合而崩掉。
四、时间操控:被低估的调试工具
UE5 的时间系统
UE5 的时间控制比原文的分层模型更灵活,因为有多个独立的时间膨胀因子:
| 控制层 | API | 作用范围 |
|---|---|---|
| 全局时间膨胀 | AWorldSettings::SetTimeDilation(Value) |
整个 World |
| Actor 自定义时间膨胀 | AActor::CustomTimeDilation |
单个 Actor 的逻辑时间 |
| Slomo 作弊 | CheatManager::Slomo(N) |
相当于调 WorldSettings + 全局 TimeDilation |
| 物理 Sub-stepping | UPhysicsSettings::MaxSubstepDeltaTime |
物理步长的最大上限 |
时间膨胀公式很简单:ΔT_game = ΔT_real × TimeDilation。
关键在于这几层可以独立控制。UI 的时间膨胀因子永远为 1.0;物理系统即使全局 TimeDilation 为 0.1,每帧仍然会按 FixedFrameRate(默认 60Hz 即 0.016s)跑多个 substep。
实战场景
慢镜头(slomo 0.1):排查碰撞 bug。遇到”有时候走不过去”类似问题,放慢 10 倍,就能看到是什么问题。
快进(slomo 10):验证长线系统。比如,装备耐久度 100 次战斗后损耗,slomo 10 后打 10 场就够了。
**playersonly**:冻结所有 AI 和物理,只剩玩家可移动。验证关卡布局时不必被敌人干扰,也用来排查”是不是物理 tick 造成了性能问题”,如果 playersonly 后帧率恢复正常,问题在 AI/物理。
极端缩放下的物理陷阱
原文提到了高速穿墙的问题。UE5(Chaos 物理引擎)的解决方案是通过 sub-stepping 控制:
1 | // UPhysicsSettings 中的关键参数 |
当 slomo 10 时,一帧的逻辑时间增量是 0.16s。如果物理固定步长是 0.016s,引擎会在这一帧内跑 10 个 substep。但如果 MaxSubsteps = 8,实际只会跑 8 步,剩余的 0.032s 被”吃掉”——物体速度低于预期,甚至可能穿透碰撞。
测试时要注意:极端 slomo 下观察到的行为,必须在 MaxSubsteps 限制的上下文里理解,不能直接等同于”玩家在正常速度下会看到什么”。
五、DemoNetDriver:UE5 内置的确定性回放
这是原文第三章最核心的话题,UE5 对此有一套完整的答案:UDemoNetDriver。
视频录像 ≠ 状态回放
很多人以为”回放”是录屏。录屏只是一串 RGBA 像素。Bug 复现需要的是每帧的游戏状态:Actor 的位置、旋转、属性值、RPC 调用序列。
UE5 的回放系统(UDemoNetDriver)做的是状态回放:它记录网络上复制的所有数据,回放时按帧重建游戏状态。
1 | 控制台命令: |
回放过程中可以用:
DemoPause/DemoPlay— 暂停/继续DemoScrubPercent N— 跳到 N% 位置DemoGoTo— 跳到指定时间点
工作原理
UDemoNetDriver 继承自 UNetDriver。录制时,它把自己作为一个”幽灵客户端”加入网络会话,拦截所有复制数据和 RPC 调用,序列化写入磁盘。回放时,它扮演服务器的角色,把录制的数据按帧喂给”幽灵客户端”——即你现在看到的回放画面。
这意味着 DemoNetDriver 记录的不是像素,是网络复制流。文件大小取决于游戏中复制了多少数据——一个简单的 FPS 回放文件可能是几 MB/分钟,一个 MMO 的复杂场景可能是几十 MB/分钟。
回放的确定性前提
回放能”完美复现”的前提是游戏逻辑是确定性的。现实是 UE5 的很多系统天然不是确定性的:
- 物理模拟(Chaos)默认使用非确定性浮点运算
- 动画蓝图中的随机节点
- 粒子系统的 GPU 模拟
- 外部数据(服务器时间、玩家输入时机等)
原文列出的破坏源在 UE5 中依然存在:
| 破坏源 | UE5 中的体现 | 对策 |
|---|---|---|
| 浮点误差 | FMath 在不同平台有不同精度 |
固定运算顺序,-deterministic 启动标志 |
| 多线程 | Task Graph 调度不可预测 | 逻辑线程与渲染线程分离,关掉并行物理 |
| 随机数 | FMath::RandRange 基于全局种子 |
FRandomStream 独立种子,录制时保存种子 |
| 时间依赖 | DeltaTime 随帧率浮动 |
固定时间步长(FApp::UseFixedTimeStep) |
Network Prediction 插件
UE5 新引入的 NetworkPrediction 插件是一个更高级的确定性方案。它基于完全确定性的模拟 + 服务器校验模型:
- 客户端以确定性方式运行模拟,预测未来 N 帧的状态
- 服务器同样以确定性方式运行,只做校验
- 一旦客户端和服务器的状态校验和不一致,服务器强制回滚
这原本是为格斗游戏/射击游戏的低延迟设计的,但它天然适合测试:如果你能让游戏逻辑跑在 Network Prediction 的确定性通道上,你就拥有了完美的回放能力——因为每一帧的输入序列和随机种子都被记录,状态完全可重建。
测试实战用法
- Bug 复现:给 QA 每人发一个带
!demorec快捷键的构建。报告 bug 时附上.replay文件。开发者拿到手就能回放,跳过”QA 说的步骤我复现不了”的困境 - 二分定位:回放时
DemoScrubPercent跳到 bug 发生前十秒,逐帧推进,精确找到触发帧 - 性能分析:回放时挂 Unreal Insights,用完全相同的游戏场景对比不同优化方案的效果——这是性能测试的黄金标准
六、构建配置:让调试后门在正式版里物理消失
原文花了很大篇幅讲作弊码的加密、混淆、服务器验证。在 UE5 的实践中,安全性不靠”把作弊码藏起来”,而是靠构建配置 + 预处理器的物理隔离。
UE5 的四种构建配置
| 配置 | 编译宏 | Console 行为 | CheatManager | 用途 |
|---|---|---|---|---|
| Debug | UE_BUILD_DEBUG |
全部可用 | 完整功能 | 开发 |
| DebugGame | — | 全部可用 | 完整功能 | 调试优化后的引擎代码 |
| Development | — | 全部可用 | 完整功能 | 日常开发、QA 测试 |
| Shipping | UE_BUILD_SHIPPING |
ECVF_Cheat 变量不可设置、不可读取 |
CheatManager 不会被实例化 |
正式发布 |
关键点:Shipping 构建中 UCheatManager 根本不会被创建。不是运行时检查”当前是不是正式环境”然后拒绝——而是代码路径在物理上不存在。任何依赖 CheatManager 的逻辑在 Shipping 中都是空操作。
1 | // Engine/Source/Runtime/Engine/Private/PlayerController.cpp (简化) |
ECVF_Cheat 的编译器级保护
同理,标记了 ECVF_Cheat 的 CVar 在 Shipping 中也是双重保护的:
1 | // ConsoleManager.cpp 中的 Set 路径 (简化) |
这意味着你在 QA 构建中可以放心添加任何调试 CVar,只要标记 ECVF_Cheat,Shipping 就自动安全。
命令注入
UE5 控制台的参数解析没有管道/重定向,所以类 Unix 的 teleport "$(rm -rf /)" 这种注入在 UE5 里不适用。但另有风险:控制台命令可以调用任何标记了 Exec 的 C++ 函数。如果一个 UFUNCTION(Exec) 内部没有对参数做校验,传入了意外的字符串,底层 C++ 代码会出现未定义行为。
实践:
APlayerController::ConsoleCommand()接受 FString 参数,任何可以调用它的人都能执行任意控制台命令- 多人游戏中,千万不要把服务器端的
ConsoleCommand()暴露给客户端 RPC - QA 构建保留完整的控制台能力即可,不需要额外的”安全层”
七、UE5 特有的调试工具
这部分是 UE5 提供的、在”传统作弊码”时代完全不存在的调试后门。
Gameplay Debugger
按 '(撇号键)激活。在关卡中覆盖显示所有 AI 决策信息:
- AI 行为树的当前节点
- 感知系统(AIPerception)检测到的敌对目标/声音/伤害源
- EQS(Environment Query System)查询的评分网格
- NavMesh 路径
测试价值:AI 行为异常的排查神器。”这个 Boss 为什么站着不动”——打开 Gameplay Debugger,看它的行为树卡在哪个节点、它的 Perception 是否”看到”了玩家、EQS 评分是否全为零。
Unreal Insights(Trace System)
Unreal Insights 记录的是引擎级别的完整时间线:每一帧、每一个 Task Graph 任务、每一个函数调用、内存分配、GPU pass。Insights 中的回放窗口支持类似”视频播放器”的操作:播放、暂停、逐帧前进、跳到指定位置。
1 | 启动录制:-trace=cpu,gpu,frame,bookmark |
测试价值:当你有一个”偶尔卡一帧”的 bug 且 DemoNetDriver 无法复现时(因为它是网络层的问题),Insights 录制 + 逐帧回放可以精确定位是哪个 Tick 函数的耗时超标。
自动化功能测试(Automation Tests)
UE5 的 GAutomator 支持 Functional Tests,可以直接调用控制台命令:
1 | IMPLEMENT_SIMPLE_AUTOMATION_TEST( |
这意味着你可以把控制台命令写进 CI 的自动化测试用例,让它们不再是一次性手敲的调试工具。







