介面自動化 – 發送請求+響應校驗

以 QQ 內一個可以隨意訪問的請求為例:

  //mma.qq.com/mqqactivity/cgi/monitor/report

請求結果:

  {“code”: 100100}

   

接下來就針對這個「介面請求」寫一個簡單的校驗腳本(拼接的參數只做參考):

思路

發出一個介面請求,並對請求結果進行校驗,比如請求是否成功,返回值的類型以及內容是否符合預期

原因

請求失敗可能是伺服器掛了,也可能是超時或者介面變更;返回值類型如果不對,可能導致客戶端崩潰或者無法處理及展示;返回值內容的檢查就是比較基礎的了,比如和資料庫或者通過計算進行判斷是否準確、滿足需要

 

import json
import requests

qq_url = "//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 = "//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() 列印出來了,如下:

 

 

溫故而知新
      – 列表
      – 元組
  
可以嘗試輸出對應的測試方法名稱,如上~~ 

 

Tags: