測試用例框架優化(一)
一、背景:
import pytest
import os # 路徑配置需要引入os 模組
import json
from common.myConf import MyConf
from common.my_path import conf_dir
from common.my_requests import MyRequests
from common.my_excel import MyExcel
from common.my_assert import MyAssert
from common.mylogger import logger
from common.my_path import testdata_dir
from common.my_data import Data #引入data 模組
from common.my_extract import extract_data_from_response
# 第一步:讀取註冊介面的測試資料 - 是個列表,列表中的每個成員,都是一個介面用例的資料。
excel_path = os.path.join(testdata_dir, "測試用例.xlsx")
print(excel_path)
me = MyExcel(excel_path, "充值介面")
cases = me.read_data()
# 第二步:遍歷測試資料,每一組資料,發起一個http的介面
# 例項化請求物件
mq = MyRequests()
massert = MyAssert()
"""
# 把前置寫在fixture裡面
# @pytest.fixture(scope="class"):表示pyteat用法的前置條件,
# 放在類裡面,這樣做的好處是不需要重複登入
"""
@pytest.fixture(scope="class")
def prepare():
# 登陸
conf = MyConf(os.path.join(conf_dir, "data.ini")) # 準備的資料
user = conf.get("normal" ,"user")
passwd = conf.get("normal", "passwd")
login_url = "member/login"
data = {"mobile_phone" :user ,"pwd" :passwd}
# 發起請求,前面已經引入了發起請求的封裝模組
resp = mq.send_requests("post", login_url, data)
# 拿token,id,leave_amount
# 因為我們並沒有把提取封裝,所以我們這裡使用json 進行提取
# leave_amount :是充值要傳的一個引數
resp_dict = resp.json()
member_id = resp_dict["data"]["id"]
token = resp_dict["data"]["token_info"]["token"]
leave_amount = resp_dict["data"]["leave_amount"]
"""
2、把前置條件當中的設定Data 屬性,然後在測試用例當中更改它的值,
如果key 已經存在,則更新他的值,如果不存在,則新增一個值
"""
setattr(Data, "token", token)
#為了方便獲取使用,把這裡改成str
# setattr(Data, "member_id", member_id)
setattr(Data, "member_id", str(member_id))
# setattr(Data, "leave_amount", leave_amount)
setattr(Data, "leave_amount", str(leave_amount))
# yield token,member_id,leave_amount 這句話就可以不要了
@pytest.mark.usefixtures("prepare")
class TestRecharge:
# 設定前置條件:
@pytest.mark.parametrize("case", cases)
def test_recharge(self, case, prepare):
# 1、在寫用例的時候,接收到上一個介面以下三個值:
# token, member_id, leave_amount = prepare #需要關聯的欄位在前置條件當中已經設定了從data當中獲取,所以這裡舊不需要再接收了,註釋掉
# 2、下一介面的請求資料中,需要替換,替換為上一個介面中提取的資料。
if case["req_data"] and case["req_data"].find('#member_id#') != -1:
# 替換掉佔位符 -
case["req_data"] = case["req_data"].replace('#member_id#', getattr(Data, "member_id"))
if case["assert_list"] and case["assert_list"].find('#leave_amount#') != -1:
# 替換掉佔位符 -
case["assert_list"] = case["assert_list"].replace('#leave_amount#', getattr(Data, "leave_amount"))
# 3、把替換之後的請求資料(json格式的字串),轉換成一個字典
req_dict = json.loads(case["req_data"])
# 4、發起請求,並接收響應結果
if hasattr(Data, "token"):
resp = mq.send_requests(case["method"], case["url"], req_dict, token=getattr(Data, "token"))
else:
resp = mq.send_requests(case["method"], case["url"], req_dict)
logger.info(resp.json())
# 5、提取響應結果中的資料,並設定為全域性變數
if case["extract"]:
# 呼叫提取處理函式
extract_data_from_response(case["extract"], resp.json())
# 結果空列表
assert_res = []
# 4、斷言響應結果中的資料sert_response_value(case["assert_list"], resp.json())
# assert_res.append(response_check_res)
if case["assert_list"]:
response_check_res = massert.as
# 5、斷言資料庫 - sql語句、結果與實際、比對的型別
if case["assert_db"]:
db_check_res = massert.assert_db(case["assert_db"])
assert_res.append(db_check_res)
# 最終的拋AsserttionError
if False in assert_res:
raise AssertionError
#從斷言響應成功的結果當中,提取leave_amount 的值,並更新全域性變數當中的eave_amount 的值
setattr(Data, "leave_amount", str(resp.json()["Data"]["leave_amount"]))
上次的一個充值介面用例當中,我自己又深入思考了以下問題,進行了優化如下:
第一:把前置條件寫在測試用例裡面
第二:從響應結果當中,提取的某欄位(leave_amount)值,並更新在全域性變數當中,也是寫在測試用例當中
第三:關於欄位需要替換的地方
首先,我們先來看前面兩個問題:
第一個問題:把前置條件寫在測試用例裡面,如果接下來新增專案,各種不同的介面所用的前置條件不一樣
那麼,你是不是得在測試用例當中寫很多很多的前置條件?那以後做業務流的時候怎麼辦呢?
第二個問題:從響應結果當中,提取leave_amount的值,並更新到全域性變數當中,但是我們思考一下,
比如上一個介面要是處理失敗了,那麼我們這一步是不是沒必要去做?是的,所以如果上一個介面處理失敗了,我們
這裡還得增加一些判斷的程式碼進行判斷處理。
——把前置介面全部寫在Excel 當中
在該用例前面增加一個我們需要用到上一個介面用例,在extract 用提取表示式寫上下一個介面需要使用到的引數,
我們在寫測試用例的時候,需不需要提取,只要判斷extract 列有沒有提取表示式,如果有,把jsonpath 的值(比如:$..token),按照
從響應結果當中提取出來重新賦值給它,再把它設定為data全域性變數,後面的測試用例當中需要用到這些值直接反問即可。
這樣我們就不需要在前置條件當中寫一堆的程式碼了,
也就是介面依賴之間的處理方法處理:
第一步:提取值:
比如:我們的充值介面,在充值之前需要登入,我們直接在Excel 表中增加一行登入的前置介面
需要注意的地方:
如果你提取的member_id 作為全域性變數,那麼你在後面使用member_id 作為全域性變數的時候,一定要前後保持一致,不然找不到,會報錯
還有就是member_id 介面本身是字串型別,就要加引號,如果不是,就不需要加引號。
我們的登入user 和passwd 也可以直接寫在 data的全域性變數當中
接下來,我們還需要對extract 列進行封裝處理
——定義一個公共的提取方法:my_extract.py
解析excel 當中extract l列,然後從響應結果中提取,然後設定Data類屬性
""" 從響應結果當中,提取值,並設定為全域性變數(Data類作為本框架的全域性變數類) 1、提取表示式:放在excel當中 (可能提取1個,可能提取多個。。以表示式個數為準,有多少個取多少個) 2、提取出來之後,設定為Data類屬性 """ import jsonpath from common.my_data import Data def extract_data_from_response(extract_epr, response_dict): """ 從響應結果當中提取值,並設定為Data類的屬性。 :param extract_epr: excel當中extract列中的提取表示式。是一個字典形式的字串。 key為全域性變數名。value為jsonpath提取表示式。 '{"token":"$..token","member_id":"$..id","leave_amount":"$..leave_amount"}' :param response: http請求之後的響應結果。要求是字典型別。,如果是字串型別需要轉換 :return:None """ # 1、從excel中讀取的提取表示式,轉成字典物件 extract_dict = eval(extract_epr) # 2、遍歷1中字典的key,value.key是全域性變數名,value是jsonpath表示式。 for key,value in extract_dict.items(): # 根據jsonpath從響應結果當中,提取真正的值。value就是jsonpath表示式 result = jsonpath.jsonpath(response_dict, value) # jsonpath找了就是列表,找不到返回False # 如果提取到了真正的值,那麼將它設定為Data類的屬性。key是全域性變數名,result[0]就是提取後的值 if result: #str(result[0]:提取之後統一轉換成字串 setattr(Data, key, str(result[0]))
總結:
處理介面關聯:
第一步:提取值
通過jsonpath從響應結果中提取,然後設定為全域性變數。我的框架中Data類用來儲存全域性變數的。
如何通過jsonpath去提取的呢?可能會提取1個值?可能會提取多個值?框架通用性,為了達到所有介面通用。
1)在excel當中添加了一列:extract。如果當前這一行的請求,有需求要從響應中提取。
那麼就在extract列對應的位置,寫上表達式。
形式是字典形式,key-value.key就是變數名,value就jsonpath提取表示式。
2)定義了一公共的提取方法:
解析excel當中extract列,然後從響應結果中提取,然後設定為Data類的屬性。
3)在測試框架的介面自動化用例當中,通過判斷extract列有沒有值,來自動提取。
第二步:替換值
————下一次分享
那麼,我們判斷extract 當中不為null,就需要提取,那麼我們的提取表示式寫在哪裡?
——》寫在發起請求之後
把提取的表示式寫在發起請求之後,這裡面的全域性變數提取就不需要了,直接去掉
到這裡,我們只是完成了一個提取的動作,還有一個替換的動作,也就是第三個問題:
第三個問題:關於欄位替換的地方,當我們介面用例很多很多,上千條時候,一個一個的替換,你能確保你每個欄位都替換了嗎?
而且上百上千個介面一個一個替換也會顯得比較麻煩。
————下一次分享
————如何更加做靈活替換
總結優化後的測試用例程式碼示例:
import pytest import os import json from common.myConf import MyConf from common.my_path import conf_dir from common.my_requests import MyRequests from common.my_excel import MyExcel from common.my_assert import MyAssert from common.mylogger import logger from common.my_path import testdata_dir from common.my_data import Data #解決day9問題 from common.my_extract import extract_data_from_response # 第一步:讀取註冊介面的測試資料 - 是個列表,列表中的每個成員,都是一個介面用例的資料。 excel_path = os.path.join(testdata_dir, "測試用例.xlsx") print(excel_path) me = MyExcel(excel_path, "充值介面") cases = me.read_data() # 第二步:遍歷測試資料,每一組資料,發起一個http的接`口 # 例項化請求物件 mq = MyRequests() massert = MyAssert() class TestRecharge: @pytest.mark.parametrize("case", cases) def test_recharge(self, case): # 2、下一介面的請求資料中,需要替換,替換為上一個介面中提取的資料。 if case["req_data"] and case["req_data"].find('#member_id#') != -1: # 替換掉佔位符 - case["req_data"] = case["req_data"].replace('#member_id#', getattr(Data, "member_id")) if case["assert_list"] and case["assert_list"].find('#leave_amount#') != -1: # 替換掉佔位符 - case["assert_list"] = case["assert_list"].replace('#leave_amount#', getattr(Data, "leave_amount")) # 3、把替換之後的請求資料(json格式的字串),轉換成一個字典 req_dict = json.loads(case["req_data"]) # 4、發起請求,並接收響應結果 #要對token進行做判斷,什麼情況下需要傳token,什麼情況下不需要傳 if hasattr(Data, "token"): resp = mq.send_requests(case["method"], case["url"], req_dict, token=getattr(Data, "token")) else: resp = mq.send_requests(case["method"], case["url"], req_dict) logger.info(resp.json()) # 5、提取響應結果中的資料,並設定為全域性變數 if case["extract"]: # 呼叫提取處理函式 extract_data_from_response(case["extract"], resp.json()) # 結果空列表 assert_res = [] # 4、斷言響應結果中的資料 if case["assert_list"]: response_check_res = massert.assert_response_value(case["assert_list"], resp.json()) assert_res.append(response_check_res) # 5、斷言資料庫 - sql語句、結果與實際、比對的型別 if case["assert_db"]: db_check_res = massert.assert_db(case["assert_db"]) assert_res.append(db_check_res) # 最終的拋AsserttionError if False in assert_res: raise AssertionError