python 編寫遊戲測試機器人客戶端 (一)
遊戲測試機器人搭建 - Player Actor
前言:
第一次寫部落格,文筆有限,加上半路出身的遊測,大佬們別往死裡噴,錯的地方請指正,在正式介紹之前先簡單說下框架:Python 的 pykka(Actor 模型),測試框架 pytest 和測試報告 allure。粗略框架介紹
閱讀前的基礎知識
Python 的 socket 程式設計,多程序,多執行緒,佇列
Actors 做什麼
當一個 actor 接收到訊息後,它能做如下三件事中的一件:
- Create more actors; 建立其他 actors
- Send messages to other actors; 向其他 actors 傳送訊息
- Designates what to do with the next message. 指定下一條訊息到來的行為
Actor 的組成
Actor 是由狀態(state)、行為(behavior)、郵箱(mailbox)三者組成的。
- 狀態(state):狀態是指 actor 物件的變數資訊,狀態由 actor 自身管理,避免併發環境下的鎖和記憶體原子性等問題。
- 行為(behavior):行為指定的是 actor 中計算邏輯,透過 actor 接收到的訊息來改變 actor 的狀態。
- 郵箱(mailbox):郵箱是 actor 之間的通訊橋樑,郵箱內部透過 FIFO 訊息佇列來儲存傳送發訊息,而接收方則從郵箱中獲取訊息。
Actors 一大重要特徵在於 actors 之間相互隔離,它們並不互相共享記憶體。這點區別於上述的物件。也就是說,一個 actor 能維持一個私有的狀態,並且這個狀態不可能被另一個 actor 所改變。
具體 Actor 介紹可參考:傳送門 >> JunChow520 的 Actor 模型介紹
pykka 框架的使用
傳送門 >> pykka 使用說明
引用說明書的例子:
# !/usr/bin/env python3
import pykka
GetMessages = object()
class PlainActor(pykka.ThreadingActor):
def __init__(self):
super().__init__()
self.stored_messages = []
def on_receive(self, message):
if message is GetMessages:
return self.stored_messages
else:
self.stored_messages.append(message)
if __name__ == '__main__':
actor = PlainActor.start()
actor.tell({'no': 'Norway', 'se': 'Sweden'})
actor.tell({'a': 3, 'b': 4, 'c': 5})
print(actor.ask(GetMessages))
actor.stop()
環境
- Python:3.7
- Windows/Linux
正文
機器人架構圖
測試機器人要用的 3 個核心 Actor(Player,Send,Recv)
Player Actor
Player 物件初始化
- 傳遞引數:使用者名稱、伺服器 IP 和 Port、性別、職業,根據專案型別增減;
- 其他 Actor 的呼叫:
- Recv Actor:必要的,socket 接收資料,後面章節介紹;
- Send Actor:必要的,socket 傳送資料,後面章節介紹;
- 可自由增加其他的 Actor;
- 初始化可設定角色的相關屬性,後面根據服務端返回資料進行相關賦值,如:角色的 ID,name,資產貨幣,攻擊防禦等 ;
# !/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File : player.py
@Contact : 512759438@qq.com
@Author : Jian
'''
import pykka
import websocket
import traceback as tb
from proto import ProtoHandler
from remote import RemoteHandler
from util import MsgSwitch, RecvActor, SendActor
TEST_CASE_CALL = None
class Player(pykka.ThreadingActor):
def __init__(self, user_name='', server='SERVER_1',sex=1, job=1):
super(Player, self).__init__()
self.host = SERVER_LIST[server]['GAME_HOST']
self.port = SERVER_LIST[server]['GAME_PORT']
self.web_host = "x.x.x.x"
self.recv_actor = None
self.send_actor = None
self.socket = None
self.proto_handler = ProtoHandler(self)
self.remote_handler = RemoteHandler(self)
'''測試用例執行時需要呼叫player'''
global TEST_CASE_CALL
TEST_CASE_CALL = self
self.player_id = None
self.state_user_id = 0
self.state_user_name = user_name
self.sys_count = 0
封裝訊息傳送,用來給自己傳送訊息或者其它 Actor 呼叫
def send_msg(self, msg_type=None, data=None):
'''
:param msg_type:訊息型別
:param data: 資料
'''
self.actor_ref.tell({
'msg': msg_type,
'data': data
})
Player Actor 例項化之後第一個執行的地方
- 不指定使用者的話則隨機賬號登入
- MSG_GUEST_LOGIN: 訊息型別
- 接下來就把訊息告訴自己,on_receive 會接收到這條訊息
def on_start(self):
if self.state_user_name is '':
self.send_msg(MSG_GUEST_LOGIN)
else:
self.send_msg(MSG_LOGIN_INFO)
接收訊息及訊息處理
- 接收的訊息會放進 MsgSwitch 進行引數檢查,MsgSwitch 參考 C 語言 switch
- 接下來是使用者註冊、創角、登入流程
- MSG_LOGIN_INFO:獲取玩家的資訊
- 獲取使用者資訊後通知 MSG_LOGIN
- MSG_LOGIN:玩家開始連線 socket,有角色則開始登入,沒有角色則通知 MSG_CREATE_PLAYER
- 連線 socket 後開啟 Send Actor 和 Recv Actor
- 給 Send Actor 傳送訊息:角色登入需要的引數,Send Actor會進行打包和發給服務端
- MSG_CREATE_PLAYER:
- 把需要的創角引數傳給Send Actor,打包後通知服務端要創角
- MSG_PROTO:Recv Actor 從 socket.recv 接收的資料進行反序列化後會傳送過來到這處理
- 如果需要對服務端返回的協議資料進行自動處理,可以在 proto 模組寫上對應處理方法,MSG_PROTO 訊息型別接收的每一條協議資料都會去 proto 模組查詢有沒有對應的處理方法 (hasattr 和 getattr)
- MSG_REMOTE_CMD:後續寫到 remote 再進行結合一起寫,不影響執行
def on_receive(self, msg):
for case in MsgSwitch(msg):
# 獲取使用者資訊
if case(MSG_LOGIN_INFO):
account_info = Account(self.state_user_name).login_info()
if account_info['code_str'] == 'OK':
user_into = account_info['user']
self.create_player_params = {
'rd3_token': user_into['token'],
'rd3_userId': user_into['userId'],
'server_list_type': 0,
'sid': 1,
'token': user_into['token'],
}
self.create_player_params.update(Account(self.state_user_name).data)
self.create_player_params.pop('password')
self.create_player_params['cmd'] = 'game_login'
self.send_msg(MSG_LOGIN)
else:print(f'獲取角色資訊ERROR, 原因: {account_info["code_str"]},{account_info["code"]}')
break
# 使用者登入
if case(MSG_LOGIN):
self.socket = websocket.create_connection(f'ws://{self.host}:{self.port}/')
self.recv_actor = RecvActor.start(self, self.socket)
self.send_actor = SendActor.start(self, self.socket)
self.send_actor.tell({MSG_PROTO: self.create_player_params})
break
# 使用者創角
if case(MSG_CREATE_PLAYER):
create_data = {
'nickname': self.state_user_name,
'rd3_token': self.create_player_params['rd3_token'],
'rd3_userId': self.create_player_params['rd3_userId'],
'sid': self.create_player_params['sid'],
'token': self.create_player_params['token'],
}
self.send_actor.tell({MSG_PROTO: create_data})
break
# 服務端返回協議處理
if case(MSG_PROTO):
method, data = msg['data']
if hasattr(self.proto_handler, method):
getattr(self.proto_handler, method)(data)
else:
print(f"沒有為協議: {method} 定義處理方法, 請前往 proto.py 檔案中定義!")
break
# 控制檯呼叫命令
if case(MSG_REMOTE_CMD):
method = msg['method']
method = (type(method) is int and "r" + str(method)) or (type(method) is str and method)
if hasattr(self.remote_handler, method):
getattr(self.remote_handler, method)(msg['data'])
else:
print(f"沒有為遠端命令: {method} 定義處理方法, 請前往 remote.py 檔案中定義!")
break
封裝遠端命令
- 角色登入後可透過 pykka.ActorRegistry.get_by_class_name('Player') 獲取例項物件用遠端命令遙控角色
def remote_msg(self, method:str=None, data=None):
'''
呼叫remote裡的方法
:param method: 方法名
:param data: 傳入的引數 元組
'''
self.actor_ref.tell({
'msg': MSG_REMOTE_CMD,
'method': method,
'data': data
})
停止 Player Actor
- 先停止其他的 Actor 再關閉 socket,最後關掉自己
def on_stop(self):
self.recv_actor.stop()
self.send_actor.stop()
self.socket.close()
self.socket.shutdown()
self.stop()
log 收集
# 列印報錯訊息
@GetLog(level='fatal')
def on_failure(self, exception_type, exception_value, traceback):
logging.fatal(f'Player: {self.state_user_name} is down.')
logging.fatal(f"ErrorType => {exception_type}")
logging.fatal(f"ErrorValue => {exception_value}")
logging.fatal(f"TraceBack => {tb.print_tb(traceback)}")
self.on_stop()
後續文章傳送門
python 編寫遊戲測試機器人客戶端 (一)
python 編寫遊戲測試機器人客戶端 (二)
python 編寫遊戲測試機器人客戶端 (三)
python 編寫遊戲測試機器人客戶端 (四)
到這裡 Player Actor 已經寫完了,目前是無法單獨執行的,需要結合後面的 Send Actor 和 Recv Actor 才能執行,寫的不清晰的歡迎在評論留言
最後的最後,各位的關注、點贊、收藏、碎銀子打賞是對我最大的支援,謝謝大家!
需要原始碼的小夥伴關注微信公眾號 ID:gameTesterGz
或掃描二維碼回覆機器人指令碼即可
相關文章
- python 編寫遊戲測試機器人客戶端 (二)Python遊戲機器人客戶端
- python 編寫遊戲測試機器人客戶端 (三)Python遊戲機器人客戶端
- 測試平臺系列(90) 編寫oss客戶端客戶端
- grpc套路客戶端編寫RPC客戶端
- 如何建立一個Java遊戲客戶端Java遊戲客戶端
- python遊戲開發實戰:網路遊戲Demo(客戶端)Python遊戲開發客戶端
- 如何編寫一個前端框架之七-客戶端路由(譯)前端框架客戶端路由
- 無線1x客戶端編寫概要客戶端
- 編寫 Netty / RPC 客戶端【框架程式碼分析】NettyRPC客戶端框架
- 使用測試客戶端「玩轉」MQTT 5.0客戶端MQQT
- 通過命令列在 Python 中測試以太坊 RPC 客戶端命令列PythonRPC客戶端
- 通過命令列在Python中測試以太坊RPC客戶端命令列PythonRPC客戶端
- 透過命令列在 Python 中測試以太坊 RPC 客戶端命令列PythonRPC客戶端
- [測試平臺] 全流程客戶端測試質量保障客戶端
- 從零開始仿寫一個BiliBili客戶端之編譯ijkplayer客戶端編譯
- 使用Python編寫一個滲透測試探測工具Python
- 用 Python 做一個 H5 遊戲機器人PythonH5遊戲機器人
- Thrift安裝與伺服器、客戶端的編寫執行演示(windows版本)伺服器客戶端Windows
- Python socket的客戶端Python客戶端
- 寫一個Flutter彩票客戶端--開獎列表Flutter客戶端
- 關於如何編寫好金融科技客戶端SDK的思考客戶端
- 客戶端效能測試利器PerfDog嚐鮮體驗客戶端
- 測試平臺系列(80) 封裝Redis客戶端封裝Redis客戶端
- 如何用python編寫猜拳小遊戲?Python遊戲
- Zookeeper C客戶端庫編譯客戶端編譯
- [譯] 如何用 Python 寫一個 Discord 機器人Python機器人
- 移動端遊戲測試一些問答遊戲
- 試著寫一下MMORPG遊戲遊戲的自動掛機遊戲
- 服務端c100k連線測試和客戶端65535測試驗證2服務端客戶端
- Skywalking PHP客戶端編譯安裝PHP客戶端編譯
- C#版Nebula客戶端編譯C#客戶端編譯
- 自己動手寫個 Android客戶端Android客戶端
- OrzClick: 國慶寫個 ClickHouse 客戶端客戶端
- Flutter寫的部落格園客戶端Flutter客戶端
- win/mac 端有哪些客戶端自動化測試的想法呢Mac客戶端
- 客戶端安裝虛擬機器問題解答?客戶端虛擬機
- 自己動手寫一個能操作redis的客戶端Redis客戶端
- python建立tcp服務端和客戶端PythonTCP服務端客戶端