介面自動化 - 傳送請求+響應校驗
以 QQ 內一個可以隨意訪問的請求為例:
https://mma.qq.com/mqqactivity/cgi/monitor/report
請求結果:
{"code": 100100}
接下來就針對這個“介面請求”寫一個簡單的校驗指令碼(拼接的引數只做參考):
思路:
發出一個介面請求,並對請求結果進行校驗,比如請求是否成功,返回值的型別以及內容是否符合預期
原因:
請求失敗可能是伺服器掛了,也可能是超時或者介面變更;返回值型別如果不對,可能導致客戶端崩潰或者無法處理及展示;返回值內容的檢查就是比較基礎的了,比如和資料庫或者通過計算進行判斷是否準確、滿足需要
import json import requests qq_url= "https://mma.qq.com/mqqactivity/cgi/monitor/report?" qq_para = { "num": "123", "test": "ceshi" } # 引數要使用json格式,所以用json.dumps()進行轉換傳輸 r = requests.get(qq_url, json.dumps(qq_para)) print(r) print(r.text) print(r.json()) code = r.json()["code"] if __name__ == "__main__": assert r.status_code == 200, "response status code is {}".format(r.status_code) assert isinstance(code, int), "code type is {}".format(type(code)) assert code == 100100, "code is {}".format(code)
說明:
1、首先需要使用pip命令下載requests 庫,匯入json 和requests 模組;
2、設定倆變數qq_url和qq_para,也可以設定成三個變數:host、path、para;
3、使用requests.get 方法傳送請求並將響應結果賦值給變數 r;
4、分別輸出r、r.text、r.json(),也可以把r.status_code 打印出來;
5、將r.json()["code"]的值賦給code,然後對響應狀態碼、響應結果的型別和值進行校驗。
執行結果:
更改三個斷言中的判斷條件,看看斷言失敗後是否會給出正確的提示資訊:
如圖,當第一條斷言失敗後,後面的 case 就不會繼續執行,而是直接丟擲一個Traceback,告訴我們斷言失敗並輸出我們指定的資訊內容,這樣可以方便我們快速定位問題,知道到底是哪裡出現了問題。當然,不寫也是 OK 的:
看執行結果,是滿足預期的。下面我們就詳細的瞭解下程式碼中的requests.get方法,原始碼如下:
看註釋,就是傳送一個 GET 請求,第一個引數是 url ;第二個是引數 params 預設值為None,所以是可選的,可傳可不傳;第三個引數 **kwargs 是可變長關鍵字引數,key - value 形式的,也是可選引數;返回的是個響應物件,可以通過句點的方式使用物件中的屬性,比如:“.text”字串形式的響應內容(str)、“.json()”使用 json 處理過的響應資料(dict)、“.status_code”HTTP的請求返回狀態,也就是響應狀態碼(比如:200、404…):
下面就根據上面的簡版程式碼做一版優化:首先,拆分一下,資料或者通用變數都放在一個檔案中(qq_data.py),請求放在一個檔案中(qq_report.py),響應資料的校驗放在一個檔案中(test_qq_report.py),這樣不管要增加多少個介面,都比較好處理。
qq_data.py
1 """ 2 基礎資料 3 """ 4 5 qq_host = "https://mma.qq.com/" 6 qq_path = "mqqactivity/cgi/monitor/report?" 7 qq_para = { 8 "num": "123", 9 "test": "ceshi" 10 }
qq_report.py
1 """ 2 隨便校驗一個網路請求 3 """ 4 5 import requests 6 import unittest 7 from homework_class import qq_data 8 9 10 class Re(unittest.TestCase): 11 12 def re_url(self, **kwargs): 13 """拼接請求連線""" 14 host = qq_data.qq_host 15 path = qq_data.qq_path 16 if kwargs: 17 para = "" 18 for key, value in kwargs.items(): 19 para += "{}={}&".format(key, value) 20 self.url = host + path + para 21 return self.url[:-1] 22 else: 23 self.url = host + path 24 return self.url 25 26 def re_response(self, **kwargs): 27 """獲取響應資料,並校驗請求是否成功""" 28 obj = requests.get(self.re_url(**kwargs)) 29 code = obj.status_code 30 self.assertEqual(code, 200, "code is {}, url is {}".format(code, self.re_url(**kwargs))) 31 return obj
test_qq_report.py
1 """ 2 隨便校驗一個網路請求 3 """ 4 import unittest 5 from homework_class import qq_report, qq_data 6 7 8 class QQReport(unittest.TestCase): 9 10 @classmethod 11 def setUpClass(cls): 12 cls.res = qq_report.Re() 13 cls.obj = cls.res.re_response(**qq_data.qq_para) 14 print(cls.obj) 15 cls.code = cls.obj.json()["code"] 16 17 def test_code_type(self): 18 """檢查code型別是否為int型別""" 19 print(type(self.code)) 20 self.assertTrue(isinstance(self.code, int)) 21 22 def test_code_value(self): 23 """檢查code是否為100100""" 24 print(self.code) 25 self.assertEqual(self.code, 100100) 26 27 if __name__ == "__main__": 28 unittest.main()
執行一下:
qq_data.py基礎資料部分沒啥可說的,後面可以直接新增不同的host或者相同 host 的不同 path 或者不同場景所需要的引數。
qq_report.py請求處理和響應獲取部分,這裡面用到了基礎資料模組中的資料,也用到了 unittest 模組,因為在 Re 類中繼承了 unittest.TestCase 類,在re_response方法中使用斷言校驗了請求是否傳送成功(因為如果請求失敗了,後面的校驗其實也沒必要,所以就寫在這裡了。但是如果多個請求互不影響,其實可以放在test裡面做校驗,避免一條請求不通過其他的case也跑不了的情況);在 re_url 方法中將 host、path 以及可能存在的引數進行拼接,並將拼接好的url返回回來。
test_qq_report.py中最先定義了一個 setUpClass 類方法,用@classmethod 裝飾器進行了裝飾,所有 case 執行前只執行一次(可以瞭解下它和 setUp() 方法的區別);後面有兩個以 test_開頭的測試方法,分別校驗了響應資料的型別和值是否符合預期。
接下來再補充一些日誌,讓執行結果看起來更清楚些:
過程:
新建一個 log.py檔案,因為我只想讓它在控制檯中輸出時間、指令碼檔名、以及我所需要輸出的資訊日誌,所以需要先匯入 logging 模組,建一個 Log 類,在構造方法中建立 logger 並設定日誌等級,然後設定日誌輸出的格式:
1 import inspect 2 import logging 3 4 5 class Log(object): 6 7 def __init__(self, name=None): 8 if name: 9 called_name = name 10 else: 11 called_name = str(inspect.stack()[1][1]).split("/")[-1] 12 self.logger = logging.getLogger(called_name) 13 self.logger.setLevel(logging.DEBUG) 14 # 日誌輸出格式 15 self.fmt = logging.Formatter('[%(asctime)s] - %(name)s -> %(levelname)s: %(message)s') 16 17 def log_print(self, level, msg): 18 # 建立一個終端Handler,用於輸出到控制檯 19 console_sh = logging.StreamHandler() 20 console_sh.setLevel(logging.DEBUG) 21 # fh = logging.FileHandler('log.txt', mode='w', encoding='UTF-8') 22 # fh.setLevel(logging.DEBUG) 23 console_sh.setFormatter(self.fmt) 24 25 self.logger.addHandler(console_sh) 26 if level == "debug": 27 self.logger.debug(msg) 28 elif level == "info": 29 self.logger.info(msg) 30 elif level == "warning": 31 self.logger.warning(msg) 32 elif level == "error": 33 self.logger.error(msg) 34 elif level == "critical": 35 self.logger.critical(msg) 36 self.logger.removeHandler(console_sh) 37 38 def debug(self, msg): 39 self.log_print("debug", msg) 40 41 def info(self, msg): 42 self.log_print("info", msg) 43 44 def warning(self, msg): 45 self.log_print("warning", msg) 46 47 def error(self, msg): 48 self.log_print("error", msg) 49 50 def critical(self, msg): 51 self.log_print("critical", msg)
我們也可以看一下Formatter的原始碼中支援哪些欄位的設定:
我這裡只用到了%(asctime)s、%(name)s、%(levelname)s、%(message)s四個欄位,對應的輸出樣式為:
[2021-07-02 20:18:11,047] - test_qq_report.py -> INFO: ---------- setUpClass ----------
下面的 log_print 方法中先建立一個終端 Handler 並且設定等級和輸出格式,然後根據不同的等級呼叫不同的方法,這裡面判斷的 5 個等級就是levelname對應的等級。之後哪個地方需要輸出日誌,匯入日誌模組,建立完例項,呼叫一下對應等級的方法就可以了。
這裡面還要詳細說一下為什麼它能夠輸出對應的指令碼檔名字,很牛批的…
就是第 11行程式碼中用到的inspect.stack()獲取呼叫棧,返回的內容是個包含元組物件的列表,程式碼中inspect.stack()[1][1]返回的就是:列表中第二個元組物件,取這個物件中第二個元素的值。這一整行的程式碼就是將剛剛取出來的值轉成字串型別,使用 “/” 進行分割,然後取最後一部分的內容賦給called_name 。這樣說起來可能比較抽象,所以我把inspect.stack()打印出來了,如下: