python 編寫遊戲測試機器人客戶端 (一)

A_Jian發表於2020-12-08

遊戲測試機器人搭建 - 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

正文

機器人架構圖

robot

測試機器人要用的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 ActorRecv 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
或掃描二維碼回覆機器人指令碼即可
微信二維碼

相關文章