1. 程式人生 > 其它 >測試用例框架優化(一)

測試用例框架優化(一)

一、背景:


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