共计 5517 个字符,预计需要花费 14 分钟才能阅读完成。
本文首发于:行者 AI
明天给大家分享一个游戏自动化测试的落地。这款游戏有独立的战斗内核负责局内战斗的计算,所以每次须要测试战斗内核时,都须要服务器重新部署,客户端(挪动端、PC 端等)从新出包,最初能力交付给测试进行测试,整个流程比拟长,也比拟耗时,所以咱们就思考在战斗内核更新时就进行测试,这样能够简化测试流程,节约工夫。
通过和内核组开发的探讨后,决定应用内核开发组提供的 QT 工具(如下图展现),在本地运行游戏的战斗内核,通过执行多个命令构建起测试的场景,再通过数据交互拿到测试数据,以此来达到咱们想要的测试目标。
1. 环境筹备
- Windows10
- Python3.7
- Allure
- Python 第三方库:pytest、allure-pytest、python-gitlab、zipp、xlrd
2. 实现流程
2.1 通信形式
启动 QT 时,启动参数中蕴含通信的端口号。QT 启动之后,应用 python 与 QT 在本地建设 socket 连贯进行通信。
def __init__(self, port):
self.port = port
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.data = {}
def connect(self):
"""建设连贯"""
localhost = socket.gethostbyname(socket.gethostname())
self.sock.connect({localhost, self.port})
将每条用例的操作命令寄存在列表中,遍历列表进行命令的发送,每条命令 json 序列化之后,通过 socket 连贯进行发送,发送实现后,对发送的命令进行判断,再做出下一步的操作。
def send(self, data):
"""发送命令"""
for d in data:
if 'sleep' == d.keys():
# 放弃 socket 连贯,向 QT 发送心跳数据
self.hearbeats(int(d['sleep'][0]), int(d['sleep'][1]))
else:
_ = json.dumps(d).encode(encoding='utf-8')
self.sock.send(_)
# 多条命令发送须要距离 0.3s
time.sleep(0.3)
# 发送初始化命令之后,期待 QT 数据初始化
if d['Pack-Field'] == 'client.initserver':
time.sleep(5)
# 当 Hread-Field== 2 时示意向内核申请以后游戏数据
if d['Hread-Field'] == '2':
# 8 个玩家数据 + 1 个游戏场景数据
self.data = [self.recv_game_data() for _ in range(9)]
接收数据时,前 8 个字节为包的大小,前面则为咱们须要的数据,因为是在本地进行的测试,所以没有对数据进行加密,接管到的数据能够间接转成 json 格局,在接收数据时会收到 QT 发过来的心跳数据,将心跳数据去除掉之后,就是咱们想要的游戏数据了。
def recv_game_data(self):
"""接管游戏数据,去除心跳数据"""
data = self.recv_data()
# 当 Hread-Field==Heartbeat 时示意数据为心跳数据
while data['Hread-Field'] == 'Heartbeat':
data = self.recv_data()
else:
return data
def recv_data(self):
size = struct.unpack('q', self.recv(8))[0]
data = json.loads(self.recv(size).decode('utf-8'))
return data
def recv(self, size):
n = 0
data = b''
while n < size:
_ = self.sock.recv(size - n)
data += _
n += len(_)
2.2 用例设计
将用例对立寄存在 Excel 文件中,每个 Sheet 为一个游戏模式,每张表都蕴含了用例编号、形容、状态、步骤、数据校验。用例执行时,依照事后设计好的步骤向 QT 发送命令构建测试场景,接管到 QT 返回的数据之后,再与数据校验中的数据进行比对,以此来校验内核性能的正确性。
用例编号 | 用例形容 | 用例状态 | 测试步骤 | 数据校验 |
---|---|---|---|---|
根底金币 -001 | 对局获胜,胜利金币 +1,根底金币 +5 | 执行 | client.initserver,<br/>client.setRound.4.0,<br/>2,<br/>client.addChessToMap.0.110011.0.0,<br/>client.goNextStage,<br/>sleep.4.5,<br/>2 | {“data_2”:{“player_0”:{“gold_increase”:6},”GameMode”:0}} |
2.3 数据校验
QT 每次返回的数据中都蕴含了 8 个玩家数据以及 1 个游戏场景数据,所以咱们依照用例中申明的校验字段,提取返回数据中绝对应的字段进行比照校验,具体的实现代码如下:
def format_data(self):
"""格式化接管到的数据"""
for _ in self.received_data.keys():
data = self.received_data[_]
self.received_data_formatted[_] = dict()
for player_num in range(len(data)):
player_data = data[player_num]
if player_num == 8:
self.received_data_formatted.get(_)['game_scene'] = player_data
else:
self.received_data_formatted.get(_)[f'player_{player_num}'] = player_data
def verify_data(self):
"""进行数据校验"""
self.format_data()
for data_key in self.assert_data.keys():
self.data_key = data_key
for check_type_1 in self.assert_data[data_key].keys():
self.check_type = check_type_1
if check_type_1 == 'GameMode':
try:
self.GameMode(data_key)
except AssertionError as e:
self.failed_dict['GameMode'] = e.args
else:
for check_type_2 in self.assert_data[data_key][check_type_1]:
try:
# 执行对应的校验模块
eval(f'self.{check_type_2}()')
except AssertionError as e:
# 捕捉校验模块返回的异样并记录
self.failed_dict[check_type_2] = e.args
数据校验中的字段如 gold_increase、GameMode 都对应一个校验的模块,gold_increase 指玩家以后金币的减少数量,GameMode 指以后的游戏模式,当然还有很多校验模块如:HandleChess、HandleEquip、ChessBoard、BoardEquip… 等等
def gold_increase(self):
# 校验金币增加值
assert_data = self.assert_data[self.data_key][self.check_type]['gold_increase']
received_data_1 = self.received_data_formatted[f'data_{int(self.data_key[-1:]) - 1}'][self.check_type]['Pack-Field']['k_19']
received_data_2 = self.received_data_formatted[self.data_key][self.check_type]['Pack-Field']['k_19']
increase_num = received_data_2-received_data_1
if increase_num != int(assert_data):
raise AssertionError(f'{self.data_key}-{self.check_type}: {increase_num} != {assert_data}')
def GameMode(self, data_key):
# 校验以后游戏模式
game_mode_assert = self.assert_data[data_key]['GameMode']
game_mode_received = self.received_data_formatted.get(data_key)['game_scene']['Pack-Field']['k_0']
assert game_mode_received == game_mode_assert,\
f'{self.data_key}-{self.check_type}: {game_mode_received} != {game_mode_assert}'
2.4 内核版本的更新
应用 python-gitlab 库下载 gitlab 上指定内核分支中的文件,下载实现后替换掉旧的文件即可,以此来更新本地的内核版本,装置 python-gitlab 库。
pip install python-gitlab
import gitlab
import os
import time
curPath = os.path.abspath(os.path.dirname(__file__))
rootPath = os.path.split(curPath)[0]
class DownloadFiles:
"""从 gitlab 上下载指定分支文件"""
def __init__(self, version):
self.dir_name = None
self.version = version
def create_dir(self):
if not os.path.isdir(self.dir_name):
os.makedirs(self.dir_name)
time.sleep(0.1)
def start_get(self):
gl = gitlab.Gitlab.from_config('xiaoming', [f'{curPath}/git.ini'])
gl.projects.list()
project = gl.projects.get(1234) # 我的项目 ID
root_path = f'{rootPath}/resource/'
info = project.repository_tree(all=True, recursive=True, as_list=True, ref=self.version)
file_list = list()
if not os.path.isdir(root_path):
os.makedirs(root_path)
os.chdir(root_path)
for info_dir in range(len(info)):
if info[info_dir]['type'] == 'tree':
self.dir_name = info[info_dir]['path']
self.create_dir()
else:
file_name = info[info_dir]['path']
# logger.info(file_name)
if file_name == 'windows.zip':
file_list.append(file_name)
if 'Datas_jit' in file_name:
file_list.append(file_name)
if 'Datas_jit/' not in str(file_list):
# raise ValueError("未检测到 Datas 相干文件,请上传")
pass
for info_file in range(len(file_list)):
get_file = project.files.get(file_path=file_list[info_file], ref=self.version)
content = get_file.decode()
with open(file_list[info_file], 'wb') as code:
logger.info(f"START-DOWNLOAD: {file_list[info_file]}")
code.write(content)
logger.info(f"DOWNLOAD COMPLETE!")
3. 不足之处
以后内核自动化测试还存在着一些不足之处:
- 用例的编写比较复杂,首先须要相熟命令,其次是校验的字段名,对数据校验的格局也有要求;
- 以后版本只能在 Windows 环境中运行,如果能够间接在测试服上运行是最好的;
- 如果 C ++ 的代码有改变的话,就须要手动更新 QT;
如果你还发现了其余的问题,欢送在评论区指出,也心愿能够和对游戏自动化测试感兴趣的敌人一起交换游戏自动化测试相干的办法和教训。