本文首發於:行者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
若是你還發現了其餘的問題,歡迎在評論區指出,也但願能夠和對遊戲自動化測試感興趣的朋友一塊兒交流遊戲自動化測試相關的方法和經驗。