1. 程式人生 > 其它 >介面自動化 - 傳送請求+響應校驗

介面自動化 - 傳送請求+響應校驗

以 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_開頭的測試方法,分別校驗了響應資料的型別和值是否符合預期。

相關內容介紹:

- unittest回顧

- setUp()方法

- import匯入

- 變長引數

- 裝飾器

- 繼承

- 類

接下來再補充一些日誌,讓執行結果看起來更清楚些:

過程

新建一個 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()打印出來了,如下:

溫故而知新
- 列表
- 元組

可以嘗試輸出對應的測試方法名稱,如上~~