一个通关率很高的机器人,交出了一份几乎空白的报告

先看一个模拟场景。假设某款动作游戏有九个区域和一个三段式Boss,团队训练了一个游戏Agent(能读取游戏状态并自己做决策的程序),它可以稳定地从主城一路打到终局。版本封板前一周,一位真人测试员在Boss二阶段的过场动画里按了快速存档,回到主城再读档,发现Boss血条还挂在屏幕上,背包里的“灰烬钥匙”也不见了。这个问题机器人一次都没有碰到过,原因很简单:在过场动画里读档对通关没有任何好处,一个只想赢的Agent没有理由这样做。

这个场景暴露的是目标问题,不是能力问题。爱游戏把“玩家Agent”和“测试Agent”拆成两类角色来设计,出发点就在这里:会玩游戏,和会测试游戏,是两个不同的任务。正常玩家不会反复开门关门一百次,不会在起跳的同一帧切换装备,不会故意在任务交付的一半退出游戏,也不会专门去试地图边缘的空气墙。但这些事情,恰恰是BUG最喜欢藏身的地方。

两类Agent到底差在哪

维度玩家Agent 测试Agent
目标 完成关卡,胜率越高越好找到预期之外的状态,并留下证据
奖励来源 通关、得分、存活时间新状态数量、异常信号、可复现程度
动作选择 逐步收敛到稳定的最优或次优策略 主动偏离常规,包含对通关无益甚至“愚蠢”的操作
对重复的态度 重复没有收益,应尽快推进 重复本身就是测试手段
对死亡和读档 死亡是代价,要尽量避免 死亡、读档、退出都是通往新状态的入口
主要产出 通关记录、录像 异常清单、状态快照、复现步骤

这张表里最容易被忽略的是“奖励来源”一行。强化学习(RL,让智能体通过试错和奖励学会策略的方法)里,奖励设计几乎决定了Agent会变成什么样子。如果测试Agent沿用玩家Agent的奖励,例如死亡扣分、耗时扣分,它就会天然回避高风险区域和低效操作,而高风险区域往往正是数值溢出、判定错位最集中的地方。

“通关率高”为什么不等于“测得好”

通关率衡量的是主路径的健壮程度,这本身有价值,比如每次提交后确认主线仍然可通,这是很好的冒烟测试。但它至少有三个盲区。

第一,路径太窄。策略训练收敛以后,动作分布的熵会不断下降,Agent每次都走几乎相同的路线,同一批状态被访问几千次,另一批状态一次都没有被访问。第二,覆盖的是“合理状态”。玩家Agent的所有行为都服务于赢,因此它几乎不会制造“背包已满时接收任务奖励”“同时持有两把互斥钥匙”这类极端状态。第三,也是最隐蔽的一点:玩家Agent可能会把BUG当成策略。假设某个版本里,站在特定台阶边缘连续翻滚能穿过Boss的房间墙壁,直接跳过战斗,这会让玩家Agent的通关率和通关速度双双提高,训练曲线看上去更漂亮,而这个穿墙问题就被奖励信号悄悄“表扬”了。

还有一种情形值得单独提醒:通关率本身会随版本波动。数值调整、怪物重排都会让玩家Agent的胜率变化,如果把“通关率下降”直接当作出了BUG的信号,会产生大量误报;反过来,通关率不变也并不代表没有回归问题,只是说明主路径没有被破坏。

所以,评价测试Agent,不应看它赢了多少局,而应看它触达了多少此前没有见过的状态,触发了多少可确认的异常,以及这些异常能不能被别人稳定复现。

测试Agent看什么、做什么

观察空间:结构化状态为主,画面做旁证

观察空间就是Agent每一步能拿到的信息。只看画面最像真人,但画面无法告诉你“任务标记是否写入存档”“背包的内部数量是否与界面一致”。爱游戏倾向的做法是双通道:以引擎导出的结构化状态为主,包括场景编号、坐标、生命与资源数值、任务标志、背包与装备、帧时间戳、最近触发的事件;再以截图作为旁证,用来发现界面与内部状态不一致、模型错位、贴图缺失这一类只有画面才看得出的问题。代价是需要游戏侧提供状态导出接口,这需要和开发团队提前约定,而不是测试阶段才去要。

动作空间:除了按键,还要有“游戏级动作”

基础输入包括移动、跳跃、攻击、交互。但很多问题出在更高一层:接受任务、放弃任务、使用道具、切换装备、存档、读档、切换地图、暂停、退出到主菜单再回来。这些动作最好被建模成一等公民,而不是让Agent通过菜单按键慢慢试出来。另外还有一类“测试专用动作”,例如直接把资源设为极端值、传送到某个检查点。它们能大幅缩短到达深层状态的时间,但有一条必须遵守:由这类动作制造出来的状态要打上标记,因为从这样的状态里发现的问题,可能是玩家永远走不到的路径,报告里需要注明“需要特殊手段才能到达”,否则开发会把时间花在不会发生的问题上。

探索策略:不是乱按,也不是死走

纯随机探索有个致命的弱点:越靠近游戏深处,状态越难碰到,因为你需要先按对一长串前置动作,才能站到“完成第三个任务、背包已满、持有火把”这个位置。纯目标驱动又会走回玩家Agent的老路。较实用的是混合策略。

  • 状态覆盖驱动:先把游戏状态做抽象,例如“区域 × 任务阶段 × 装备组合”,为每个抽象状态记访问次数,优先扩展访问次数少的状态。
  • 好奇心驱动:对“预测不准”的状态给额外奖励。Agent对某个操作后果预测得越差,说明它对这个位置越不了解,越值得多试。
  • 目标加随机:先用脚本或玩家Agent把角色送到较深的状态,再切换成扰动模式,在那里做异常顺序、重复、中断等操作,相当于先搭好梯子再向四周发散。

具体的“奇怪操作”有哪些、各自容易撞出哪一类问题,爱游戏测试AI的设计文章里,我们在测试机器人为什么不该像普通玩家那样玩中按类别展开过,这里只强调一点:扰动要有目标,每一次异常操作都应该对应一个假设,比如“这个任务在交付前退出,会不会丢失奖励标记”,而不是把手柄按键随机打乱。

异常信号:怎样知道“不对劲”

探索得再多,如果不能判断什么是异常,就只是在跑步。常见的信号大致分成两档。

第一档比较容易机械判断:崩溃与卡死(角色持续输入,但状态指纹连续多帧不变)、数值越界(生命为负数、金币溢出、耐久超过上限)、越界坐标(掉出地图、嵌进几何体)、性能异常(帧时间尖峰、内存持续增长)。第二档才是难点:逻辑上不对,但游戏没有崩溃。任务显示已完成而NPC仍然说“你还没做完”,装备栏里两件互斥的武器同时生效,读档以后Boss血条还在。这一档需要对“预期行为”有明确定义,也就是所谓测试Oracle(用来判断结果是否符合预期的依据)。它单独值得写一篇,见AI自动测试如何判断一个结果是不是BUG。缺少Oracle的测试Agent,只能发现第一档问题,对第二档基本无能为力。

一次测试运行大概长什么样

把上面几部分串起来,用刚才的灰烬回廊来走一遍。假设测试Agent已经用脚本把角色送到了Boss二阶段的过场动画,此时切换成扰动模式,它会依次做几件玩家几乎不会做的事,并在每一步之后对照状态。

[t+0s]  场景=boss_arena  阶段=2过场  背包.灰烬钥匙=1
[t+2s]  动作=快速存档
[t+3s]  动作=退出到主城
[t+9s]  动作=读档
[t+10s] 检查: 背包.灰烬钥匙=0  (预期1)  -> 异常A
[t+10s] 检查: Boss血条可见=是, 场景=main_city -> 异常B

这段记录里有两个值得注意的地方。一是异常A和异常B都不会导致崩溃,靠“通关是否成功”永远发现不了;二是记录里保存了阶段、动作顺序和检查项,开发拿到以后不用重新猜测触发条件。至于这个问题究竟是存档序列化遗漏,还是过场状态没有被清理,仍然需要人来定位,Agent能做的是把“它在什么状态下、做了什么、什么东西不对”交代清楚。

与真人测试怎么分工

需要强调,测试Agent不能完全替代真人。大致的分工是这样的:

  • Agent擅长:夜间批量运行、成千上万次重复、组合爆炸的排列、每次提交后的回归检查、极端资源状态的枚举。
  • 真人擅长:判断手感是否顺、节奏是否拖、提示是否让人看懂、难度是否公平、美术和文案给人的感受。这些没有可以直接计算的数值,也很难写成规则。
  • 需要协作:Agent发现的“疑似卡死”,有时其实是设计好的等待或演出;由真人来排序、确认,并决定是否值得修。

爱游戏更倾向的流程,是让Agent把大量“可能有问题”的状态压缩成一份带证据的清单,再交给真人去判断哪些是真的,哪些值得优先处理。

落地时最容易踩的三个坑

  1. 把玩家Agent改个名字当测试Agent。它能通关,看上去成果很好,实际上覆盖面窄,且奖励信号还会鼓励利用BUG。
  2. 惩罚死亡和低效。这会让测试Agent避开最危险、最值得测的区域。测试阶段应把死亡当作中性事件,甚至当作有用的状态转移。
  3. 不留记录。发现异常的那一刻如果没有保存足够的状态与操作序列,就无法交给开发复现,这一点在长时间运行后才暴露的BUG里会更加突出。

回到开头那个漏掉过场读档问题的机器人。它需要的不是更强的通关能力,而是另一个目标:在每个状态里想一想,有什么不合常理的事可以试,试完以后有没有东西对不上。