HttpRunner 二次開發之 Dubbo 介面自動化

大佬喝可乐發表於2020-07-20

國內大部分公司目前都是使用基於 Java 語言的 Dubbo 技術棧,而測試同事普遍對 Python 技術棧更為熟悉。為了使不懂 JAVA 程式碼的測試同事也能進行 Dubbo 介面層的測試,故對 HttpRunner 進行二次開發,新增對 Dubbo 介面的支援

1、實現原理
關於 HttpRunner 我想不用多做介紹,測試小夥伴應該都瞭解,這是一款非常優秀的面向 HTTP(S) 協議的通用測試框架,我們要做的是基於這個框架進行二次開發。

根據 Dubbo 官方文件中提到的:dubbo 可以透過 telnet 命令進行服務治理,詳情見​​Dubbo 官方文件

而在 Python 中有一個第三方包 telnetlib,所以我們可以透過這個包來執行 telnet 命令,進而對 dubbo 介面進行呼叫

透過上圖我們還了解到一個資訊點,那就是如果我要透過 telnet 連線伺服器,我需要 ip 還有 埠號。下面,讓我們一步步來實現

2、Dubbo 服務圖解
​​
​​

我們可以透過 Dubbo 服務的架構圖瞭解到,要獲取 Dubbo 服務,我們需要去 zookeeper 服務註冊中心找到對應的服務即可

3、telnet 連線 Dubbo 服務
使用 Docker 容器部署微服務,每次重啟服務 ip 都會發生變化(原因:容器間則不用 ip 直接通訊,而使用主機名、復服務名、網路別名),所以在使用 telnet 連線時需要獲取動態的 ip,我司專案目前使用的 dubbo-monitor 進行管理

透過爬蟲的思想,可動態獲取服務 ip 和埠號,連線上伺服器之後,我們就可以透過模擬命令列對服務進行治理

4、如何使用
4.1、請求服務相關程式碼

這段程式碼是處理請求的程式碼,放在 httprunner 原始碼檔案下的 utils 資料夾內

import time
import os
import sys
import telnetlib


class TelnetClient(object):
    """透過telnet連線dubbo服務, 執行shell命令, 可用來呼叫dubbo介面
    """

    def __init__(self, server_host, server_port):
        self.tn = telnetlib.Telnet()
        self.server_host = server_host
        self.server_port = server_port

    # 此函式實現telnet登入主機
    def connect_dubbo(self):
        try:
            print("telent連線dubbo服務端: telnet {} {} ……".format(self.server_host, self.server_port))
            self.tn.open(self.server_host, port=self.server_port)
            return True
        except Exception as e:
            print('連線失敗, 原因是: {}'.format(str(e)))
            return False

    # 執行傳過來的命令,並輸出其執行結果
    def execute_some_command(self, command):
        # 執行命令
        cmd = (command + '\n').encode("gbk")
        self.tn.write(cmd)

        # 獲取命令結果,字串型別
        retry_count = 0
        # 如果響應未及時返回,則等待後重新讀取,並記錄重試次數
        result = self.tn.read_very_eager().decode(encoding='gbk')
        while result == '':
            time.sleep(1)
            result = self.tn.read_very_eager().decode(encoding='gbk')
            retry_count += 1
        return result

    # 退出telnet
    def logout_host(self):
        self.tn.write(b"exit\n")
        print("登出成功")


class InvokeDubboApi(object):

    def __init__(self, server_host, server_port):
        try:
            self.telnet_client = TelnetClient(server_host, server_port)
            self.login_flag = self.telnet_client.connect_dubbo()
        except Exception as e:
            print("invokedubboapi init error" + str(e))



    def invoke_dubbo_api(self, dubbo_service, dubbor_method, *args):
        api_name = dubbo_service + "." + dubbor_method + "{}"
        cmd = "invoke " + api_name.format(args)
        print("呼叫命令是:{}".format(cmd))
        resp0 = None
        try:
            if self.login_flag:
                resp0 = self.telnet_client.execute_some_command(cmd)
                print("介面響應是,resp={}".format(resp0))
                # dubbo介面返回的資料中有 elapsed: 4 ms. 耗時,需要使用elapsed 進行切割
                return str(re.compile(".+").findall(resp0).pop(0)).split("elapsed").pop(0).strip()
            else:
                print("登陸失敗!")
        except Exception as e:
            raise Exception("呼叫介面異常, 介面響應是resp={}, 異常資訊為:{}".format(resp0, str(e)))
        self.logout()

    def logout(self):
        self.telnet_client.logout_host()

class GetDubboService2(object):
    def __init__(self):
        pass

    def get_dubbo_info2(self,content):
        try:
            dubbore = re.compile(r"([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)", re.I)
            result = dubbore.search(str(content)).group()
            print("獲取到dubbo部署資訊" + result)
            return {"server_host": result.split(":")[0], "server_port": result.split(":")[1]}
        except Exception as e:
            raise Exception("獲取dubbo部署資訊失敗:{}".format(str(e)))

這段程式碼放在 functions 中,這樣在 yml 檔案中可以直接透過 ${方法名}進行呼叫

​​

程式碼如下:

def invoke_dubbo2(content, dubbo_service, dubbo_method, *args):
    '''
    透過 zk管理後臺查詢dubbo註冊資訊
    :param dubbo_service:   dubbo中 服務名 如:com.zl.mall.api.IItemService
    :param dubbor_method:  服務中的方法 如:updateItem
    :param args: 方法請求需要的引數
    :return:
    '''
    dubbo_info = GetDubboService2().get_dubbo_info2(content)
    invokeDubboApi = InvokeDubboApi(server_host=dubbo_info.get("server_host"),
                                    server_port=dubbo_info.get("server_port"))
    return invokeDubboApi.invoke_dubbo_api(dubbo_service, dubbo_method, *args)

將程式碼新增至框架之後,即可在 yml 檔案中使用

4.2、新增 testcase

和在專案中填寫 Http testcase 類似,其中新增的幾個 variables 引數為

dubbo_service: com.zl.item.api.IItemService ---Dubbo 服務名稱(必填)
dubbo_method: queryItemByLstItemId ---Dubbo 服務方法名(必填)
iItemIdList: [123,12323] ---方法的請求引數(根據 Dubbo 定義的方法來調整入參)

${invoke_dubbo2()} 請求 dubbo 介面的方法,按照對應的格式填寫即可請求 DUbbo 介面

4.3、執行 testcase

執行 testcase,檢視控制檯資訊,其中 resp 即為 dubbo 介面返回的資訊,對比 validate 中設定的預期值,即可完成對 Dubbo 介面的測試

5、注意事項
5.0、請求引數異常

請求 Dubbo 介面如果填入的引數有誤,會報 no such method 的錯誤,請檢查一下引數是否正常

5.1、列舉類請求:

列舉類的類名: com.zl.item.entity.StudentEnum

需要使用到的列舉類:GOOD_STUDENT,填寫格式如下 :

{"name": "GOOD_STUDENT", "class": "com.zl.item.entity.StudentEnum "}

5.2、實體類請求:

實體類類名:com.zl.item.entity.Student

實體類的欄位:

public class student{
Long id;
String code}

{"class":"com.zl.item.entity.Student","Id":123,"code":"abc"}

5.3、對於請求方法是 void 方法的校驗設定

java 程式碼中 void 這種方法是沒有返回值的,所以我們直接用 “null”。如果需要進一步校驗資料的準確性,可以校驗這個方法改變的特性

5.4、對於入參是 Boolean 型別的資料

在 json 中,直接 使用{“data”:true}即可,但是在 python 請求時需要使用 “true”,加上雙引號

相關文章