如果你刚踏进游戏测试这行,或者正打算从点点点的功能测试转向自动化,第一个让你犯难的问题大概率不是“Python怎么学”,而是:游戏这种UI动不动就重做、状态复杂得像个迷宫、还整天和服务器性能死磕的软件,到底该用什么测试框架?
我做了几年游戏测试,从手游到PC客户端都碰过,踩过不少坑,也把市面上一堆测试工具挨个试了个遍。这篇就说说我现在项目里实际在用的8个Python测试框架(工具),它们不是拿来充门面的,而是真的能解决游戏测试里“用例怎么组织、UI怎么自动化、后端怎么压测、数值怎么校验”这四类核心问题的组合。
先说结论:真正专门为游戏设计的框架其实只有两个,剩下六个都是从通用测试生态里挑出来、再按游戏场景重新组合的。这种“组合拳”的思路,恰恰是很多新手最容易想偏的地方——总以为游戏测试有什么神兵利器,实际上高手都在拼装工具。
1. 游戏测试有自己的脾气:框架选型前必须先想清楚的事
1.1 游戏测试和普通软件测试的差别在哪
普通Web系统测的是表单、接口、权限,整个UI是可控的,元素有id、name,点击有稳定的路径。游戏完全不是这样。
第一个麻烦是UI极度不稳定。手游一个版本换一套UI布局太正常了,今天按钮在右下角,明天就挪到左上角,美术换张图也能让界面大变样。如果你依赖“固定坐标”或者“固定元素名称”去做自动化,一个版本更新就能让脚本全线崩溃。
第二个麻烦是状态机和随机性。游戏里有大量状态切换:待机、战斗、结算、复活、抽卡、掉线重连。每一条状态路径都可能有独立的逻辑坑。再加上暴击、掉落、技能随机数值,测试用例的数据范围会膨胀到难以手工穷举。
第三个麻烦是性能问题常驻。游戏卡不卡、加载快不快、服务器能不能扛住万人同时在线,这些不是“能用”就行,而是要持续量化。普通软件测试框架不会关心你前端一秒渲染多少个粒子,或者后端每秒能处理多少次战斗结算。
1.2 框架选型不能看名气,要看能不能嵌入游戏测试的三层工作
我后来把游戏测试工作拆成三层,选型就有谱了:
- 底层是用例组织和执行:环境怎么切换、用例怎么依赖、报告怎么出、能不能并行跑。
- 中间层是客户端自动化:游戏界面上的操作怎么模拟,点击、拖拽、截图比对、控件定位。
- 上层是服务端和数值验证:压测、随机数据校验、边界条件。
围绕这三层,我最终固定下来的组合是:pytest搭配Hypothesis管底层和数值,Airtest搭配Poco管手游UI,PyAutoGUI负责桌面端兜底,Appium负责真机分发,Selenium负责H5活动页,Locust负责后端压测。下面逐个讲,先说为什么这么配,再给可以直接用的例子。
2. 日常用例管理这块,pytest 和 Hypothesis 是我雷打不动的底座
2.1 pytest:游戏测试用例组织的底座
pytest在游戏测试里经常被低估,大家总觉得它就是个“写断言跑用例”的东西。但实际上游戏项目最需要的是它的fixture机制和插件生态。
游戏测试有个典型痛点:同一个用例要在不同环境跑——开发服、测试服、预发布服,服务器地址不一样,玩家账号不一样,充值状态也不一样。用pytest的fixture可以很优雅地解决。
# conftest.py import pytest from game_client import GameClient @pytest.fixture(scope="session") def beta_client(): """连接测试服的客户端实例""" client = GameClient(server="beta", version="0.9.2") client.login(username="tester_001", password="******") yield client client.logout() @pytest.fixture(scope="session") def pre_release_client(): """连接预发布服的客户端实例,数据更干净""" client = GameClient(server="pre_release", version="0.9.2") client.login(username="tester_002", password="******") yield client client.logout()这样用例里只需要声明参数就会自动拿到对应环境的客户端。我在同一个用例集里跑两个环境的回归,一点都不用改测试函数。
pytest另外两个实用点是插件。pytest-xdist可以并行执行,pytest-html或allure-pytest能出漂亮的HTML报告,发给策划和开发看的时候很有说服力。实际项目里我通常这样跑:
pytest tests/test_quest_flow.py -n 4 --dist loadscope --alluredir=./allure-results-n 4是4个进程并行,--dist loadscope保证同一个模块的用例不跨进程,避免多个用例同时抢一个游戏客户端的尴尬。
2.2 Hypothesis:给游戏数值校验装上一把“自动扫雷枪”
游戏测试里最容易出隐蔽Bug的就是数值公式和随机逻辑。比如伤害计算,如果策划写了个公式y = attack * 2.5 - defense * 0.8,你手工测几个点根本看不出问题,但攻击力拉满、防御为负、Buff叠加到极限的时候可能就溢出或者算出负数。
Hypothesis是一个基于属性测试的库,你给它一个规则,它会自动生成海量边界数据去轰你的函数。用在游戏数值校验上非常合适。
from hypothesis import given, strategies as st from combat import calculate_damage @given( attack=st.integers(min_value=0, max_value=100000), defense=st.integers(min_value=-1000, max_value=100000), crit=st.floats(min_value=0.0, max_value=2.0), damage_reduction=st.floats(min_value=0.0, max_value=0.9) ) def test_damage_never_negative(attack, defense, crit, damage_reduction): result = calculate_damage(attack, defense, crit, damage_reduction) # 伤害值不能为负,也不能超过一个合理的上限 assert 0 <= result <= 500000这段代码看着简单,但实际跑的时候Hypothesis会生成几千组组合,包括攻击力0、防御负数、暴击倍率极大、减伤接近1这样手工很难想到的边界。我第一次在某个卡牌项目的抽卡概率模块上用这个思路,半小时就抓到了两个概率核对不上的边界Bug。
这里说下我的个人习惯:凡是游戏里涉及“公式计算”的模块——伤害、经验、金币掉落、抽卡概率——我第一版测试就上Hypothesis。它不能替代正常用例,但能帮你兜住那些“想都没想过”的输入。
3. Airtest 和 Poco:游戏UI自动化的主力军
3.1 Airtest:靠图像识别兜住“看不见元素”的局面
Airtest是网易开源的跨平台UI自动化工具,对游戏测试来说,它最值钱的能力是图像识别。游戏里的角色、技能图标、地图物件,很多没有现成的控件属性,但Airtest能通过截图模板匹配的方式把它们抓出来。
我用Airtest最典型的场景是自动化跑通新手引导。新手引导是游戏测试的苦活累活,改版频繁、步骤又多,手动点一遍至少半小时,用Airtest可以压缩到几分钟。
from airtest.core.api import * # 自动设置环境,连接当前设备 auto_setup(__file__, logdir=True, devices=["Android:///", "Windows:///"]) start_app("com.example.game") # 等待开屏出现,timeout控制在20秒 wait(Template("splash_screen.png", threshold=0.8), timeout=20) # 点击“开始游戏”按钮 touch(Template("start_button.png", threshold=0.8)) # 滑动角色选择列表 swipe((700, 500), (200, 500), duration=0.5) # 断言进入主界面 assert_exists(Template("main_city.png", threshold=0.8), "应该进入主城界面")注意threshold这个参数,默认是0.7,但我一般调到0.8到0.85之间。阈值低了会把相似的图标误识别成目标,阈值高了反而在特效遮挡时找不到图。这个值没有标准答案,只能在具体场景里试。
Airtest还有一个我很喜欢的设计:每个脚本执行时都会自动录屏和截日志。出了问题回看日志里的画面记录,比开发给个“复现不了”三个字有用得多。
3.2 Poco:从“找图片”升级到“找控件”
Airtest的短板在于图像识别在动态特效、分辨率适配面前不够稳定,特别是3D游戏里视角一转,同一个按钮的截图就完全变了。这时候就得靠Poco。
Poco也是网易开源的一套UI控件定位框架,它能在Unity、Unreal、Cocos以及部分自研引擎里直接拿到游戏的UI控件树——类似Web里通过DOM拿元素。
from poco.drivers.unity3d import UnityPoco poco = UnityPoco() poco("Canvas", "MainPanel", "StartButton").click() # 等待加载条出现再消失 loading_bar = poco("Canvas", "LoadingBar") loading_bar.wait_for_appearance(timeout=10) loading_bar.wait_for_disappearance(timeout=30) # 断言背包按钮可见 if not poco("Canvas", "MainPanel", "BagButton").exists(): raise AssertionError("进入主城后背包按钮应该可见")因为Poco拿的是控件层级而不是截图,所以分辨率变了、UI微调了,只要控件名字没变,脚本就还能跑。我在实际项目里的策略是:**能用Poco的用Poco,Poco控不住的地方再用Airtest图像识别做兜底。**两者可以写在一个脚本里协作,这也是Airtest和Poco官方推荐的搭配方式。
3.3 一个完整的自动化回归小例子
把Airtest和Poco配合起来的日常脚本大概是这样的逻辑:用Poco识别底层控件,用Airtest处理那些没有控件属性的美术资源,最后把状态同步给pytest做断言。
拿“自动打副本”场景举例:
from airtest.core.api import * from poco.drivers.unity3d import UnityPoco poco = UnityPoco() def enter_dungeon(): poco("Canvas", "DungeonBtn").click() if not poco("Canvas", "ConfirmPanel").exists(): raise AssertionError("点击副本按钮后应出现确认面板") poco("Canvas", "ConfirmPanel", "EnterBtn").click() def wait_battle_end(timeout=120): end_flag = poco("Canvas", "VictoryPanel") end_flag.wait_for_appearance(timeout=timeout) def run(): enter_dungeon() wait_battle_end() # Airtest负责点击结算界面的“再来一次” touch(Template("again_button.png", threshold=0.85)) return True这个脚本我每天挂在游戏客户端上定时跑,早上看报告就行,不用真坐在电脑前手动点一上午。它最能解决的是那种“改个数值就得全流程跑一遍”的回归场景。
4. PyAutoGUI、Appium、Selenium:按平台补全自动化拼图
4.1 PyAutoGUI:桌面端和“非主流环境”的兜底
不是所有游戏客户端都能接Poco的。老项目、外包版本、各种渠道特供包,很可能没有适配UI控件树,甚至有些就是PC端的Flash或者用自研渲染引擎做的。这时候PyAutoGUI是我用得最多的兜底方案。
PyAutoGUI的核心能力是模拟鼠标键盘操作,再配合屏幕图像识别做定位。
import pyautogui import time # 找到登录按钮的位置并点击 login_btn = pyautogui.locateCenterOnScreen("login_btn.png", confidence=0.8) if login_btn is None: raise RuntimeError("没有找到登录按钮,检查当前窗口和截图是否匹配") pyautogui.click(login_btn) # 输入账号 time.sleep(0.5) pyautogui.write("tester_003", interval=0.05) # 按下回车确认 pyautogui.press("enter")但PyAutoGUI有一个大坑我必须提醒:它相信“屏幕看到的”,所以分辨率一变、窗口一缩、游戏视角一转,locateCenterOnScreen很可能找不到目标。用之前一定要固定游戏窗口大小,最好固定为1920x1080全屏模式。还有,它不能穿透锁屏界面,跑自动化的时候Windows别设自动锁屏,不然第二天来看脚本在锁屏界面卡死一整夜。
4.2 Appium:真机分发的移动客户端自动化
手游测试里除了用模拟器,还有一大块是分发到不同真机上的渠道包。这些包通常没有Airtest那种直接连PC的便利,而Appium在Android和iOS的自动化上有比较成熟的生态。
Appium的用法和Selenium很像,本质上是把手机里的App当成一个Web页面去操作。它的能力边界在游戏里主要体现在“App层面的操作”和“原生控件的处理”,比如登录页的输入框、权限弹窗、内部SDK的WebView页面。
from appium import webdriver desired_caps = { "platformName": "Android", "platformVersion": "13", "deviceName": "emulator-5554", "appPackage": "com.example.game", "appActivity": ".MainActivity", "noReset": True, "newCommandTimeout": 300 } driver = webdriver.Remote("http://localhost:4723/wd/hub", desired_caps) # 点击系统权限弹窗的“允许” permission_allow = driver.find_element("id", "com.android.permissioncontroller:id/permission_allow_button") permission_allow.click()Appium的优点是协议标准、语言无关、跨平台,缺点是慢和依赖资源多。我在真机分发场景用它做“安装、启动、权限、登录、首包更新”这条冒烟链路,但不会拿它去做深度游戏战斗自动化,深度逻辑还是交给Poco更现实。
4.3 Selenium:H5游戏和Web活动页不能忘
现在很多游戏都有配套的H5页面,比如活动公告页、签到领奖页、游戏官网的试玩Demo,甚至部分休闲游戏重心就在浏览器里。这些页面的自动化用Selenium是最顺手的。
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver = webdriver.Chrome() driver.get("https://example-game.com/events") wait = WebDriverWait(driver, 10) sign_btn = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[@data-event='sign']"))) sign_btn.click() assert "签到成功" in driver.page_source写H5自动化的要点是别硬等,用显式等待代替time.sleep(),页面加载快就快跑,加载慢也不会误报。另外H5页面里经常会嵌WebGL,这时候Selenium只能操作外层的DOM,WebGL内部的操作还是得靠Airtest截图识别,两个框架可以交替使用。
5. Locust:给游戏后端做一次真实的压力体检
5.1 为什么游戏压测不直接用传统的线程压测工具
游戏服务端压测的需求和其他后端不太一样:玩家行为高度动态,登录、进副本、打怪、购买、聊天是混合发生的;每一步都有随机等待;而且玩家之间的行为不是均匀的,会有人反复进出房间、有人挂机不动。
传统的JMeter按“固定线程数+固定间隔”发请求,模拟不了这种真实游戏玩家的节奏。Locust的优势就在于它是基于协程的,可以轻松模拟几千上万个并发玩家,而且每个玩家的行为可以写得非常接近真实用户。
5.2 一个典型的游戏后端压测脚本
下面是我在项目里用过的简化版压测脚本,模拟玩家做“登录-查任务-开战斗”的循环:
from locust import HttpUser, task, between import random class PlayerUser(HttpUser): # 每个玩家操作之间随机等待1到3秒 wait_time = between(1, 3) def on_start(self): """玩家进入游戏时先登录""" resp = self.client.post("/api/login", json={ "player": f"load_{random.randint(1, 100000)}", "token": "test_token" }) if resp.status_code != 200: raise Exception("登录失败") @task(3) def get_current_quest(self): """查看当前任务,权重高,模拟常见行为""" self.client.get("/api/quest/current") @task(1) def start_battle(self): """进入一次战斗,权重低,模拟低频高消耗行为""" self.client.post("/api/battle/start", json={ "quest_id": random.randint(1001, 1020) }) @task(1) def buy_item(self): """购买道具,低频操作""" self.client.post("/api/shop/buy", json={"item_id": 5001})跑起来就一条命令:
locust -f loadtest/locustfile.py --headless -u 2000 -r 50 -t 10m-u 2000是模拟2000个玩家,-r 50是每秒启动50个,-t 10m是持续压10分钟。压完看两个核心指标:响应时间的P95、P99,以及事务失败率。如果P99超过500毫秒,或者错误率超过0.1%,就得排查是代码瓶颈还是数据库问题。
Locust还支持分布式压测,一个master节点加多个worker节点能模拟几万人。做新活动上线前的容量评估时,这个能力很关键。
6. 落地经验:8个框架怎么分工、踩过哪些坑、怎么从零搭一套
6.1 一张表看完8个框架的分工
| 场景 | 首选框架 | 辅助/替代 | 选型理由 |
|---|---|---|---|
| 用例组织、断言、测试报告 | pytest | pytest-xdist / allure | fixture机制适合多环境切换,插件生态成熟 |
| 游戏UI自动化(跨平台) | Airtest | Poco | 图像识别兜底,Poco稳定识别控件树 |
| 桌面客户端无SDK的老游戏 | PyAutoGUI | Airtest Windows模式 | 轻量,但需要固定分辨率和色彩 |
| 真机/渠道包冒烟测试 | Appium | Airtest | 跨平台标准API,适合多设备分发 |
| H5活动页、Web游戏 | Selenium | playwright | 浏览器生态成熟,可复用Web测试经验 |
| 后端并发压测 | Locust | JMeter | 协程模型能模拟真实玩家行为 |
| 数值公式、随机逻辑校验 | Hypothesis | 自定义随机工具 | 自动生成海量边界数据,补盲区 |
这个分工不是死规定,核心原则是“用能拿到最稳定控件的方式去拿控件,拿不到再用图像识别,最后才用坐标模拟”。这条优先级顺序能帮你省掉一半的脚本维护成本。
6.2 接游戏测试自动化时最常见的几个坑
先说Airtest和Poco混用的坑。Airtest的touch(Template(...))默认会根据图片匹配结果点击中心点,但如果游戏画面有动态特效一直在变化,匹配率会忽高忽低。我的做法是:如果某个图标在连续10次脚本执行中有3次找不到,就不要再调阈值了,直接换Poco定位。图像识别是兜底,不是主力。
再说Poco在自研引擎里的适配问题。Poco官方支持Unity、UE、Cocos,但国内很多自研引擎需要额外接入Poco的Agent。如果项目不允许改客户端代码,那就只能老老实实靠Airtest纯图像识别。这个要在框架选型前就和客户端团队沟通好,不然后面改架构很痛。
然后是pytest和Airtest的集成问题。Airtest脚本往往面向单设备,pytest做并行执行时如果直接-n 4跑4条Airtest用例,会同时操作同一台设备导致互相抢焦点。解决方案是每个测试用例绑定不同设备,或者给用例加锁。我现在用的是pytest的devicefixture配合设备配置文件,让每条用例知道“该连哪个设备”,这样并行才安全。
Locust的坑主要在登录态管理上。游戏后端基本都有登录态,Locust的on_start如果不做登录,后续请求要么大量401,要么压测流量根本没到达目标接口。压测前先在脚本里打印几个状态码,确认登录通过再放量。
6.3 如果从零开始搭,我的建议路线
我经常被刚入行的同事问:这么多框架,先学哪个?
我的建议是分三步走。第一步,先学pytest,把用例结构、fixture、报告跑通,这是所有自动化的地基,将来不管跑Airtest还是Appium,最后都要回到pytest来组织用例。第二步,学Airtest加Poco,挑一个你项目里最常见的核心玩法流程,写一个自动跑通的脚本,这个成就感会非常强,也能最快让团队看到价值。第三步,等前面的框架稳定了,再上Locust做压测、用Hypothesis校验数值公式。
这个顺序的好处是:每一步都有产出,不会学了三个月还在写demo。我见过太多人第一周就开始研究怎么搭一套“完美”的自动化平台,结果一个月过去自动化脚本一行都没跑在真实游戏上。先跑起来,再谈完美。
另外想提醒一点:这些框架的名字在游戏测试面试里也经常被问到。面试官问“你怎么做游戏自动化”,你如果能把“pytest组织用例、Airtest和Poco做UI识别、Locust压后端、Hypothesis查数值边界”这套组合讲清楚,再举一个实际案例,通常比背一堆八股文更有说服力。
我在实际项目里的体会是,工具永远是围绕流程转的。游戏测试自动化不一定要用最高级的黑科技,重要的是你手上的框架能不能覆盖用例组织、UI自动化、性能压测、数值验证这四件事。这8个框架的组合,我用了两年多,最大的价值不只是省人力和提效率,而是让测试能把精力放到真正需要人判断的地方去。