首页 / 资讯中心 / 文章详情

从DOOM到自动化测试:AI Agent决策模型迁移实战

从DOOM到自动化测试:AI Agent决策模型迁移实战 ★ FEATURED ARTICLE
1. 从游戏画面到测试脚本这个项目到底在做什么第一次看到“Jev”这个项目名加上“DOOM”和“自动化测试”这两个词摆在一起我脑子里蹦出来的第一个念头是这俩东西怎么能扯上关系一个是上世纪九十年代把第一人称射击游戏推向神坛的经典之作一个是现代软件工程里枯燥但必不可少的质量保障环节。但仔细琢磨了一下我发现这个组合其实非常巧妙——它本质上是在用一个极简的、规则明确的虚拟环境来训练和验证一个AI Agent的决策模型然后再把这套决策能力迁移到自动化测试的场景里去。说白了这个项目的核心思路是让AI学会“玩游戏”再把玩游戏学到的决策逻辑用到“跑测试”这件事上。你可能会问游戏和测试有什么共通之处共通之处就在于两者都是在有限状态下做出一系列动作最终达成某个目标。DOOM里Agent要在血量、弹药、敌人位置这些状态变量下决定是开枪、移动还是找掩体自动化测试里Agent要在页面元素、接口返回、日志输出这些状态变量下决定是点击、输入、断言还是跳过。两者的底层逻辑惊人地相似。这个项目适合谁看如果你是对AI Agent决策模型感兴趣但不知道从哪下手的人或者你是测试工程师想了解AI怎么帮自己减轻重复劳动又或者你是个游戏AI爱好者想看看经典游戏怎么被拿来当训练场那这篇内容应该能给你不少可参考的东西。我会尽量把里面涉及到的技术选型、实现细节、踩坑经验都摊开来讲让你看完能自己动手搭一个类似的框架出来。2. 为什么选DOOM作为决策模型的训练场2.1 DOOM作为强化学习环境的天然优势选DOOM来做AI Agent的训练环境不是拍脑袋决定的。我试过好几个环境最后发现DOOM有几个别的环境很难同时具备的优点。第一状态空间足够丰富但又不至于爆炸。DOOM的画面是像素级的但游戏逻辑本身是离散的——地图是网格化的敌人有固定的行为模式武器有明确的伤害数值。这意味着Agent需要处理的信息量很大但又不是那种完全无法建模的混沌系统。相比之下有些现代3D游戏画面太复杂Agent光是在里面“看懂”场景就要消耗大量算力训练效率极低。第二奖励信号非常明确。击杀敌人给分捡到物品给分通关给分死亡扣分。这种清晰的奖励结构对强化学习来说太重要了。我见过一些环境奖励设计得很模糊Agent训练半天都不知道自己到底做对了什么。DOOM在这方面几乎是教科书级别的。第三环境本身足够轻量。DOOM的原始代码只有几万行经过社区几十年的维护和移植现在有各种语言版本的实现。这意味着你可以在普通笔记本上跑起来不需要什么高端显卡集群。对于个人开发者或者小团队来说这个门槛太关键了。2.2 从游戏决策到测试决策的迁移逻辑那问题来了DOOM里学会的决策怎么迁移到自动化测试里这里面的关键桥梁是状态-动作-奖励三元组的抽象。在DOOM里状态可以抽象成当前血量、当前弹药量、视野内敌人数量、距离最近敌人的距离、当前武器类型。动作可以抽象成前进、后退、左移、右移、射击、切换武器。奖励就是击杀得分减去受伤扣分。在自动化测试里状态可以抽象成当前页面URL、页面元素可见性、接口响应状态码、响应时间、日志中的错误关键字。动作可以抽象成点击元素、输入文本、发送请求、等待、断言。奖励就是测试通过得分减去测试失败或超时扣分。你看只要把这两套东西映射到同一个抽象框架里Agent学到的“在什么状态下做什么动作能最大化长期奖励”这个策略就可以在两个领域之间迁移。当然迁移不是零成本的后面我会讲怎么处理迁移过程中的适配问题。2.3 方案选型背后的取舍在具体实现上我考虑过几种方案。一种是直接用深度强化学习从像素输入开始训练。这个方案理论上最通用但实际训练成本太高而且DOOM的像素输入里有很多冗余信息比如天花板纹理、地板花纹这些对决策其实没什么帮助。另一种是手工提取状态特征把游戏画面转成结构化的状态向量。这个方案训练效率高很多但需要人工设计特征有一定的主观性。我最后选的是混合方案用轻量级的卷积网络处理画面提取出高层特征再和手工提取的状态特征拼接起来一起输入决策网络。这样既保留了画面中的空间信息又利用了领域知识加速训练。实测下来这个方案在训练速度和最终效果上都比纯端到端或者纯手工特征要好。3. 决策模型的核心架构拆解3.1 状态编码器的设计细节状态编码器是整个决策模型的眼睛它负责把原始的游戏画面和状态数据转换成网络能理解的向量。我把它分成两个分支来处理。画面分支用的是一个小型的卷积网络结构是三层卷积加两层全连接。输入是缩放后的灰度画面分辨率压到84x84。为什么用灰度而不是彩色因为DOOM里的颜色信息对决策帮助不大但彩色输入会让参数量翻三倍训练时间也相应增加。灰度化之后画面里的关键信息——敌人轮廓、墙壁边缘、物品形状——都还在足够Agent做判断了。状态分支处理的是数值型状态包括血量、弹药、位置坐标、朝向角度这些。这些数值的量纲差异很大血量可能是0到100坐标可能是0到几千。直接输入网络会导致梯度不稳定所以我在输入前做了一步归一化把所有数值都缩放到0到1之间。这个操作看起来简单但不做的话训练很容易发散我在这上面踩过坑。两个分支的输出拼接成一个固定长度的向量再经过一层全连接做融合。融合层的维度我试过128、256、512最后发现256在表达能力和计算开销之间平衡得最好。3.2 动作空间的离散化处理DOOM的原始动作空间是连续的——你可以用摇杆控制移动方向和速度用鼠标控制视角。但连续动作空间对强化学习来说太难了训练不稳定收敛慢。所以我把动作空间离散化了。具体来说我把移动方向分成8个离散方向加上“不动”一共9种。射击动作分成“开火”和“不开火”两种。切换武器分成“切换”和“不切换”两种。这样组合起来总的动作空间大小是9乘以2乘以2等于36种。这个规模对DQN或者PPO这类算法来说刚刚好既不会太小导致策略表达受限也不会太大导致训练困难。注意离散化粒度太粗会导致Agent行为僵硬比如只能走八个方向遇到斜向的敌人可能打不准。粒度太细又会让动作空间爆炸。我的经验是先粗后细先用粗粒度训练出一个能通关的模型再逐步增加动作粒度做微调。3.3 奖励函数的设计与调优奖励函数是强化学习的指挥棒设计得好不好直接决定Agent学出来的行为是否符合预期。我最初的设计很简单击杀敌人加10分捡到物品加5分通关加100分死亡扣50分。但训练出来的Agent有个毛病——它特别喜欢站在原地和敌人对射因为这样击杀效率最高但血量掉得也快经常在后期因为血量不足而失败。后来我调整了奖励结构加入了几个新的项。一是血量惩罚每损失一点血量扣0.1分这样Agent会主动躲避攻击。二是时间惩罚每过一帧扣0.01分这样Agent不会磨磨蹭蹭。三是探索奖励走到之前没去过的区域加2分鼓励Agent探索地图而不是蹲在一个角落。调整之后Agent的行为明显变得更“聪明”了——它会边打边退会绕路去捡血包会主动寻找通往下一关的出口。这个调优过程我反复了七八轮每一轮都要重新训练几个小时但效果提升是肉眼可见的。3.4 训练算法的选择与对比训练算法我主要对比了DQN、PPO和A2C三种。DQN是值函数方法适合离散动作空间训练稳定但样本效率低。PPO是策略梯度方法样本效率高但调参敏感。A2C是PPO的简化版训练更快但方差大。在DOOM这个环境里我最后选了PPO。原因是DOOM的状态空间虽然离散化处理了但状态本身还是高维的值函数方法在这种高维空间里容易过估计。PPO的裁剪机制能有效限制策略更新幅度训练过程更稳。而且PPO对超参数的容忍度比A2C高不需要太精细的调参就能跑出不错的结果。具体参数上学习率设成3e-4裁剪系数0.2折扣因子0.99GAE参数0.95。这些数值是我在多次实验后固定下来的换环境可能需要重新调但在DOOM上表现很稳定。4. 从游戏到测试迁移过程的关键步骤4.1 测试场景的状态抽象方法把DOOM里训练好的决策模型迁移到自动化测试第一步是重新定义状态空间。测试场景的状态和游戏场景完全不同需要重新设计抽象层。我用的方法是分层抽象。最底层是原始数据比如DOM树、网络请求记录、控制台日志。中间层是特征提取从原始数据里抽取出有意义的信号比如“某个按钮是否可点击”、“某个接口是否返回200”、“日志里是否有error关键字”。最高层是决策状态把中间层特征组合成固定长度的向量输入给决策网络。这个分层设计的好处是底层和中间层可以用规则或者传统脚本实现不需要AI参与只有最高层的决策才交给模型。这样既降低了模型的学习难度又保留了传统测试脚本的可解释性。4.2 动作空间的重新映射测试场景的动作空间和游戏完全不同。游戏里是移动和射击测试里是点击、输入、等待、断言。但抽象层面是一样的——都是在当前状态下选择一个动作然后观察结果。我把测试动作分成几类交互类点击、输入、滚动、等待类固定等待、条件等待、验证类断言元素存在、断言文本内容、断言接口状态、控制类跳过、重试、终止。每一类下面再细分具体动作比如点击可以细分成点击按钮、点击链接、点击复选框。动作空间的大小取决于被测系统的复杂度。对于简单的页面可能只有几十种动作对于复杂的企业级应用可能有几百种。动作空间太大的话可以先用规则做一层过滤把明显不合理的动作排除掉再让模型在剩余动作里做选择。4.3 奖励函数的重新设计测试场景的奖励函数和游戏也不一样。游戏里是击杀和通关测试里是测试通过率和执行效率。我设计的奖励函数包含这几项测试步骤通过加1分测试步骤失败扣2分测试用例整体通过加10分测试用例失败扣20分执行时间超过阈值扣5分。另外还有一个稳定性奖励如果同一个测试用例连续多次执行结果一致加3分鼓励模型学出稳定的测试策略。这里有个坑要注意测试失败的惩罚不能设得太高否则模型会学出一种“什么都不做就不会失败”的保守策略。我一开始把失败惩罚设成-10结果模型学会了直接跳过所有可能失败的步骤测试通过率是上去了但覆盖率惨不忍睹。后来把惩罚降到-2同时增加了覆盖率奖励模型才学会了在风险和收益之间做平衡。4.4 迁移学习的具体实现迁移学习的实现方式有两种一种是微调把DOOM上训练好的网络参数作为初始值在测试数据上继续训练另一种是特征复用只复用状态编码器的底层参数决策层重新训练。我两种都试过。微调的方式收敛更快但容易过拟合到DOOM的决策模式上比如模型会倾向于“主动出击”而不是“谨慎验证”这在测试场景里是不合适的。特征复用的方式收敛慢一些但最终效果更好模型能学出更符合测试逻辑的策略。最后我选的是部分微调冻结状态编码器的前两层只微调后面的层和决策层。这样既利用了DOOM上学到的底层特征提取能力又给了模型足够的自由度去适应测试场景。5. 实操过程与核心环节实现5.1 环境搭建与依赖安装整个项目的环境搭建分成两块游戏环境和测试环境。游戏环境我用的是社区维护的一个轻量级DOOM实现编译安装很直接。依赖主要是CMake和SDL2在Ubuntu上几条命令就能搞定。需要注意的是这个实现默认是给人类玩家玩的要改成AI控制需要把输入接口暴露出来。我改了一个版本把键盘输入替换成网络接口AI通过socket发送动作指令游戏返回当前画面和状态。测试环境用的是Playwright加Pytest的组合。Playwright负责浏览器控制Pytest负责测试组织和报告生成。这两个工具都是Python生态里比较成熟的文档齐全社区活跃遇到问题容易找到解决方案。# 游戏环境依赖 sudo apt-get install cmake libsdl2-dev libsdl2-net-dev # 测试环境依赖 pip install playwright pytest pytest-html playwright install chromium5.2 状态采集与预处理流水线状态采集是连接游戏和模型的桥梁。在游戏端我每帧采集一次画面和状态数据画面缩放到84x84灰度图状态数据归一化后打包成一个字典。这个字典通过socket发送给模型端。模型端收到数据后先做一轮预处理。画面数据转成numpy数组除以255做归一化。状态数据按预设的均值和方差做标准化。然后两部分数据分别输入编码器的两个分支。这里有个性能优化的点如果每帧都做完整的预处理帧率会掉得很厉害。我的做法是跳帧采集每4帧采集一次状态中间帧的动作保持不变。这样既保证了决策的时效性又降低了计算压力。实测下来跳帧采集对最终效果的影响很小但训练速度提升了将近一倍。5.3 模型训练与调参实录训练过程我跑了大概三天用的是单张消费级显卡。总共训练了200万步前100万步在DOOM上做预训练后100万步在测试场景上做微调。预训练阶段前10万步基本是随机探索Agent在地图里乱走偶尔开枪。10万步之后开始出现有目的的行为比如主动靠近敌人、捡拾物品。50万步左右能稳定通关第一关。100万步时能通关前三关但第四关的Boss战还是打不过。微调阶段前10万步模型表现很差测试通过率只有30%左右比随机策略好不了多少。20万步之后开始有明显提升通过率爬到60%。50万步时达到75%左右之后提升变缓。最后稳定在80%上下剩下的20%主要是那些需要复杂业务逻辑判断的用例模型确实搞不定。调参过程中我记录了一些关键指标的变化曲线这里列一个简表供参考。训练阶段步数游戏得分测试通过率平均执行时间预训练10万120--预训练50万450--预训练100万780--微调120万-45%12.3s微调150万-68%9.8s微调200万-80%8.1s5.4 测试用例的自动生成与执行模型训练好之后我把它接入到测试执行流程里。具体做法是给模型一个初始页面URL和一个测试目标比如“完成用户注册流程”模型自己决定每一步做什么动作直到达成目标或者触发终止条件。测试用例的生成是动态的不是预先写死的。模型每走一步就把当前状态和选择的动作记录下来形成一个执行轨迹。这个轨迹可以导出成Pytest脚本供人工审查和回归使用。这里有个细节值得说模型生成的测试脚本有时候会包含一些“奇怪”的操作比如连续点击同一个按钮三次。人工审查时可能会觉得这是冗余操作但实际上模型可能是在等待某个异步加载完成。所以审查的时候不能只看动作本身还要结合状态变化来判断合理性。6. 常见问题与排查技巧实录6.1 训练不收敛的排查思路训练不收敛是强化学习里最常见的问题没有之一。我遇到过的表现有奖励曲线一直震荡、损失函数不下降、Agent行为随机。排查的时候我一般按这个顺序来。先看奖励尺度。如果奖励的绝对值太大或太小梯度会不稳定。我的经验是把单步奖励控制在-1到1之间总回报控制在-100到100之间。如果超出这个范围就做缩放。再看网络结构。层数太深或者太宽都可能导致训练困难。我一般从简单的结构开始比如两层全连接加一层输出能跑通再逐步加深。DOOM这个任务其实不需要太深的网络三层卷积加两层全连接足够了。最后看超参数。学习率是最敏感的3e-4是个比较安全的起点。如果训练不稳定先降学习率试试。折扣因子0.99适合大多数场景但如果任务周期很短可以降到0.95。6.2 模型在测试场景表现不佳的调优方法模型在游戏里表现很好一到测试场景就拉胯这个我遇到过好几次。原因通常是状态分布不匹配。游戏里的状态是连续的、平滑变化的测试里的状态是离散的、跳跃的。模型习惯了处理连续状态突然面对离散状态会不适应。解决方法有两个。一是状态平滑在测试状态输入模型之前做一个滑动平均把离散状态变得稍微连续一些。二是课程学习先让模型在简单的测试场景上训练逐步增加复杂度而不是一上来就扔到最复杂的场景里。还有一个原因是动作空间不匹配。游戏里的动作是移动和射击测试里的动作是点击和输入。模型可能学会了“移动”这个动作在游戏里的价值但不知道“点击”在测试里意味着什么。这时候需要重新初始化决策层的参数让模型从头学习测试动作的价值。6.3 执行效率优化的几个关键点模型跑起来之后执行效率是个大问题。我最初版本跑一个测试用例要30多秒后来优化到8秒左右。主要做了这几件事。减少模型推理次数。不是每一步都需要模型决策有些步骤可以用规则直接处理。比如“等待页面加载”这种动作用规则判断加载完成就行不需要模型参与。我把模型决策的频率从每步一次降到每三秒一次效率提升很明显。批处理状态采集。多个测试用例并行执行时状态采集可以批量化。一次采集多个页面的状态一起输入模型一起返回动作。这样能充分利用GPU的并行能力。缓存模型输出。有些状态是重复出现的比如登录页面。模型对同一个状态的决策结果是一样的可以缓存起来下次遇到直接查缓存不用重新推理。6.4 常见问题速查表问题现象可能原因排查方法解决方案训练奖励不上升奖励尺度不当打印每步奖励值缩放奖励到-1到1模型行为随机探索率过高检查epsilon值逐步降低探索率测试通过率低状态分布不匹配对比游戏和测试状态统计状态平滑或课程学习执行速度慢推理频率过高统计每步耗时降低决策频率或批处理模型过拟合训练数据太少对比训练和验证表现增加数据或加正则化动作空间太大动作设计太细统计动作使用分布合并低频动作提示排查问题时先确认环境本身没问题再怀疑模型。我遇到过好几次是环境配置错了比如状态采集的坐标系搞反了导致模型看到的状态和实际不符。这种问题从模型侧很难发现一定要先验证环境。7. 这套方案还能怎么扩展这个项目做下来我觉得最有价值的不是最终的那个模型而是这套从游戏环境到测试环境的迁移框架。框架本身是通用的换一个游戏、换一个测试场景大部分代码都能复用。扩展方向有几个。一是换更复杂的游戏比如一些策略类游戏状态空间更大决策链条更长能训练出更强的决策能力。二是换更复杂的测试场景比如移动端测试、接口测试、性能测试每种场景的状态和动作定义都不一样但抽象框架是一样的。三是多Agent协作让多个Agent分别负责不同的测试模块通过通信机制协调决策这个在大型系统的测试里应该很有用。我个人在实际操作中的体会是这套方案最适合的场景是回归测试和探索性测试。回归测试的特点是步骤固定、重复执行模型可以学出最优的执行路径。探索性测试的特点是路径不固定、需要随机应变模型可以学出人类测试工程师的探索策略。至于那些需要复杂业务判断的测试目前模型还搞不定还是得靠人工。最后再分享一个小技巧训练模型的时候可以同时开两个环境一个用来训练一个用来做验证。每训练一万步就在验证环境上跑一次完整测试记录通过率和执行时间。这样能及时发现过拟合也能直观看到模型的进步曲线。我一开始没做验证闷头训练了50万步才发现模型早就过拟合了白白浪费了两天时间。
阅读完成 · 觉得有帮助?
咨询建站