本文首发于:行者AI
今天给你们分享一个游戏自动化测试的落地。这款游戏有独立的战斗内核负责局内战斗的计算,因此每次须要测试战斗内核时,都须要服务器从新部署,客户端(移动端、PC端等)从新出包,最后才能交付给测试进行测试,整个流程比较长,也比较耗时,因此咱们就考虑在战斗内核更新时就进行测试,这样能够简化测试流程,节约时间。python
通过和内核组开发的探讨后,决定使用内核开发组提供的QT工具(以下图展现),在本地运行游戏的战斗内核,经过执行多个命令构建起测试的场景,再经过数据交互拿到测试数据,以此来达到咱们想要的测试目的。 git
启动QT时,启动参数中包含通信的端口号。QT启动以后,使用python与QT在本地创建socket链接进行通信。shell
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链接进行发送,发送完成后,对发送的命令进行判断,再作出下一步的操做。json
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发过来的心跳数据,将心跳数据去除掉以后,就是咱们想要的游戏数据了。windows
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(_)
将用例统一存放在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}} |
QT每次返回的数据中都包含了8个玩家数据以及1个游戏场景数据,因此咱们按照用例中声明的校验字段,提取返回数据中相对应的字段进行对比校验,具体的实现代码以下:app
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...等等socket
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}'
使用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!")
当前内核自动化测试还存在着一些不足之处:gitlab
若是你还发现了其余的问题,欢迎在评论区指出,也但愿能够和对游戏自动化测试感兴趣的朋友一块儿交流游戏自动化测试相关的方法和经验。