遊戲測試-戰鬥內核的自動化測試

本文首發於:行者AI

今天給你們分享一個遊戲自動化測試的落地。這款遊戲有獨立的戰鬥內核負責局內戰鬥的計算,因此每次須要測試戰鬥內核時,都須要服務器從新部署,客戶端(移動端、PC端等)從新出包,最後才能交付給測試進行測試,整個流程比較長,也比較耗時,因此咱們就考慮在戰鬥內核更新時就進行測試,這樣能夠簡化測試流程,節約時間。python

通過和內核組開發的探討後,決定使用內核開發組提供的QT工具(以下圖展現),在本地運行遊戲的戰鬥內核,經過執行多個命令構建起測試的場景,再經過數據交互拿到測試數據,以此來達到咱們想要的測試目的。 git

QT運行截圖

1. 環境準備

  • Windows10
  • Python3.7
  • Allure
  • Python第三方庫:pytest、allure-pytest、python-gitlab、zipp、xlrd

2. 實現流程

2.1 通信方式

啓動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(_)

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個遊戲場景數據,因此咱們按照用例中聲明的校驗字段,提取返回數據中相對應的字段進行對比校驗,具體的實現代碼以下: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}'

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. 不足之處

當前內核自動化測試還存在着一些不足之處:gitlab

  • 用例的編寫比較複雜,首先須要熟悉命令,其次是校驗的字段名,對數據校驗的格式也有要求;
  • 當前版本只能在Windows環境中運行,若是能夠直接在測試服上運行是最好的;
  • 若是C++的代碼有改動的話,就須要手動更新QT;

若是你還發現了其餘的問題,歡迎在評論區指出,也但願能夠和對遊戲自動化測試感興趣的朋友一塊兒交流遊戲自動化測試相關的方法和經驗。

相關文章
相關標籤/搜索