浅谈游戏客户端自动化测试
正文
—————总该会点自动化了吧
游戏的自动化测试一直是不少测试人员热衷于讨论的话题之一,但这种技术其实并不新鲜,无论是传统的互联网行业还是游戏行业,都会大量使用自动化测试技术。
简单聊聊自动化测试
—————先进行初步了解定义吧
既然要做自动化,就得先知道什么是自动化。简单地说,就是把平时测试手工执行测试用例的过程,转为工具或者脚本来执行。
但其实,这并没有确切的定义, 笔者更加偏向认为这是个比较泛化的概念,只要使用了程序化的手段去完成测试的流程,就可以定义为自动化测试。
为什么要做自动化测试
既然自动化测试如此场景,那必然有其可取之处,笔者认为最突出的优点包括:
提升测试效率:常见的例子就是每日冒烟这种重复性极高的测试工作,或者兼容性测试这种步骤简单但重复工作量极大的工作。使用自动化测试可以释放这部分人力,让测试的哥们儿有更多时间深入的进行测试。
进行手工难以执行的极端测试:通常包括超长时间的客户端稳定性测试,或者概率测试。使用自动化脚本长时间保持客户端运行,确认客户端长时间运行的稳定性如何。概率测试简单地说就是超高数量级的抽卡,确认抽卡概率符合预期。
自动化测试方案
—————控件搜索or图像识别?
自动化的对象是游戏,而游戏通常的载体有:客户端、服务端、编辑器。而针对游戏客户端的自动化也是最常见到的,本文主要也是基于游戏客户端进行讨论。
主流的自动化思路有两种:一种是图形识别,另一种是控件操作。典型的例子就是网易的Airtest和腾讯的GAutomator。这里简单说下两者的特点。
图像识别
无入侵:作为测试人员,可能会没有权限得到项目源码然后使用编辑器打开,再嵌入你需要的SDK。而对游戏无入侵的图像识别变成了十分突出的优点。
效率较低:图像识别毕竟的步骤是截图和图像处理,这都是比较慢的步骤。想要识别游戏内出现时间较短的UI(如击杀提示等),就会十分不可靠。
维护较难:对于开发阶段的游戏,通常会存在大量临时UI。每次更新UI时都需要维护一次脚本,自然提高了维护成本。
图像识别:图像识别的最大优点是什么?那自然是可以识别图像(什么废话)。对于需要确认界面正确性,常见的方法是与一张预期内的界面截图作比较,而这通常是控件搜索方案难以实现的。
控件操作
高效率:对比图像识别方案,因为无需截图和图像处理,自然提高了许多效率。
较复杂:因为需要将相关SDK嵌入游戏中以获取游戏控件信息,所以部署起来自然是比图像识别麻烦的。
易于维护:通常只要和项目组的开发哥们儿约好,不要随意改控件的名称,维护起来就会简单许多。
在实现期间,图像识别、控件操作通常是互相结合的,这也是为什么Airtest会集成了POCO。通常来说如果测试有拿到项目的源码权限的话,优先考虑控件操作为主的方案,再辅以图像识别。如果没权限那就只能图像识别了。
具体实现
—————一点个人经验
用例管理
初次尝试自动化的时候,用的是Airtest提供的IDE,回想起来用例管理和代码调试用那个IDE简直就是灾难,真不知道当时怎么坚持写下去还让别的同事跑起来的(?
通常,在用例设计的时候,不能出现一条用例引用另一条用例的情况,如果两条用例之间有重复的共性,可以把这一共性新建为一个方法。
以json为例,简单展示下笔者在json的测试用例模板:
{
"用例全局参数1": 1,
"用例全局参数2": 2,
"caseList": [
{
"index": 0,
"name": "用例名称",
"stepWaitTime": 5,
"该条用例参数1": 1,
"该条用例参数2": 2,
"stepList": [
{
"index": 0,
"stepName": "步骤名称",
"该步骤参数1": 1,
"该步骤参数1": 2
},
{
"index": 1,
"stepName": "步骤名2",
......
}
]
},
{
"index": 2,
"name": "用例名称2",
......
}
]
}
模块分层
OK,我们来回忆下大学那过时课本上学到的软件经典分层架构:表示层、数据访问层、业务逻辑层。分层有诸多好处:减少耦合、提高复用性、提高可维护性等。我们的测试脚本相对正式的大规模开发来说,减少很多因为每层边界带来的管理成本,同时一定程度上减少了因为多层调度带来的性能下降问题。
当然,做一套自动化并不需要完整参照这分层架构,我们可以鉴于分层的优点出发,尝试设计自己的分层模块。可能的分层如下:
框架底层
- 测试框架源码:存放GAutomator、Aitrtest源码等
- 其他基础组件:设备连接、设备操作、数据处理、报告输出等
- 基础配置:主要包括运行时的配置管理、环境配置管理等
用例层
- 用例库:存放用例的位置
- 数据库:存放执行日志、测试结果相关的位置
业务层
- 原测试框架中的接口无法满足需求时,进行二次开发、上层封装的内容
- 仅适用于当前游戏项目的工具、SDK等内容
二次开发
GAutomator是一个相对轻量级的自动化测试框架,里面提供的接口并不多,通常需要进行二次开发。通常,若存在原有接口无法满足需求的情况,会进行二次开发,通常二次开发的主要目的,是为了方便用例编写时复用。
通常可以通过两种方式去进行抽象:
- 玩家单个操作
- 固化自动化流程
先说第一种:
比如说要针对游戏内的载具进行遍历的自动化测试,那必然每个载具都需要重复「上车」和「下车」的动作,那我们就可以抽象出这两个动作,进行二次开发,GAutomator的代码如下:
TESTRESTPYE = Tuple[bool, str]
def go_aboard(device: Device) -> TESTRESTPYE:
"""上车"""
engine = device.engine_connector()
if isinstance(engine, GAutomator):
engine: GAutomator
// keyevent是GA中仅适用于PC或编辑器的模拟键盘映射方法
// 游戏内按「F」上车
ret = engine.keyevent(KeyCode.F)[0]
print(f"上车结果:{ret}, {type(ret)}")
if ret == RespStatus.SuccessJson.value:
return True, "上车成功"
return False, "上车失败"
TESTRESTPYE = Tuple[bool, str]
def car_check(device: Device, car_location: list) -> TESTRESTPYE:
// 传送至车辆隔壁,执行成功标记step为True
step = move_to_the_car(device: Device, car_location)
if step:
// 上车
step = go_aboard(device: Device)
if step:
// 驾驶车辆
step = move_forward(device: Device)
if step:
// 下车
step = go_aboard(device: Device)
if step:
return True, "车辆遍历通过"
return False, "车辆遍历不通过"
结语
—————继续加油吧!

这篇文章写的东西还是比较浅,自动化测试技术很多东西都没有提及到,像是自动化流程的稳定性保障、多机监控、前置环境准备、输出测试报告、可视化操作窗口等......
但无论如何保持技术的积累是必须的,这也是本文想要传达的主要观点:技术技术能力会束缚你的测试思想,同样也会拓宽你的测试思想。在了解更多的技术之后,才会知道自己能用什么样的技术去组织自己的测试策略,或者说已有的测试策略上能做怎样的补充。