這次,我掌握了 pytest 中 fixture 的使用及 pytest 執行測試的載入順序

耿晓發表於2024-05-15

先說經驗總結

  • pytest 會在測試函式呼叫時自動查詢具有相同名稱的 fixture,並將其注入到測試函式中。
  • pytest 在執行測試時會先進行測試用例的收集,然後再執行測試。

實操:[造數函式] 和 [測試用例] 都需要呼叫登入介面獲取使用者 token,既然都需要呼叫登入介面,那會不會出現 token 失效的情況呢?

例如有一個訂單查詢介面 orderList,呼叫 orderList 需要傳入的引數為:token、orderType、submitState、page 和 limit,其中 submitState 的可選值是固定的 [None,1,2],orderType 的可選值是由介面 getOrderType 返回的,呼叫 getOrderType 需要傳入 token。在這種場景下設計用例就引發了一系列的思考。

首先我共有三個.py 檔案,test.py,conftest.py 和 case_data.py,內容大致如下:

在寫 case_data 時,先後共寫過兩個版本,我描述一下思考過程,當前為第一個版本。

# conftest.py
@pytest.fixture(scope="session")
def tf_stu_token():
    base_url = Environment.HOST_LAPI + 'login'
    data = {
            'userphone': Environment.userphone_stu,
            'password': CT.to_md5(Environment.password_stu),
            'phonecode': 86,
            'type': 1
            }
    resp = requests.post(url=base_url, data=data)
    assert resp.status_code == 200
    token = resp.json()["data"]["token"]
    return token
# case_data.py V1

def getOrderType(tf_stu_token):  # !問題就出在這裡,在非測試函式的地方引用fixture並不會生效,但當時不知道
    url = Environment.HOST_LAPI + 'getOrderType'
    data = {
        'token':tf_stu_token,
    }
    resp = requests.post(url=url,data=data)
    return resp.json()['data']  # 會返回[0,1,2,3,4]

def data_orderList():
    total = []

    orderType_list = getOrderType()
    submitState_list = [None,1,2]

    for i in range(5):
        item = []
        case = {}
        case['OrderType'] = orderType_list[i % len(orderType_list)]
        case['submitState'] = submitState_list[i % len(submitState_list)]

        item.append(case)

        expect = {}
        expect['code'] = 2000
        item.append(expect)

        item.append('正常入參')

        total.append(item)

    # token為空
    total.append([{'OrderType': 0,'submitState': None}, {'code': 4001}, 'token為空'])

    return total

'''
這個total最終為包含6條測試資料的列表:
 [
    [{'OrderType': 0,'submitState': None}, {'code': 2000}, '正常入參'],
    [{'OrderType': 1,'submitState': 1}, {'code': 2000}, '正常入參'],
    [{'OrderType': 2,'submitState': 2}, {'code': 2000}, '正常入參'],
    [{'OrderType': 3,'submitState': None}, {'code': 2000}, '正常入參'],
    [{'OrderType': 4,'submitState': 1}, {'code': 2000}, '正常入參'],
    [{'OrderType': 0,'submitState': None}, {'code': 4001}, 'token為空']
]
'''
# test.py
import pytest
import requests
import case_data as CD

@ pytest.mark.parametrize("case, expect, desc", CD.data_orderList())
def test_orderList(tf_stu_token, case, expect, desc):
    url = Environment.HOST_LAPI + 'orderList'
    data = {
        'token': tf_stu_token,
        'orderType':case['orderType'],
        'submitState':case['submitState'],
        'page': 1,
        'limit': 20
    }
    resp = requests.post(url=url, data=data)
    assert resp.json()['code'] == expect['code']

測試後發現,造數函式報錯,原因是呼叫 getOrderType 時缺少引數 tf_stu_token。當時就很納悶,我隱約記得 fixture 函式不需要顯式呼叫呀,不應該是執行的時候會自動載入麼。報錯資訊大致如下:

查閱後發現,pytest 會在測試函式中呼叫時自動查詢具有相同名稱的 fixture,並將其注入到測試函式中。非測試函式並不能使用 fixture。 也有資料說 fixture 可以被非測試函式呼叫,但幾經實踐後並沒有成功。
於是為了在 case_data.py 中獲取 token,我決定在 case_data.py 中再另行呼叫一遍登入介面。修改後的 case_data.py 如下:

# case_data.py V2

def get_stu_token(userphone, password):
    base_url = Environment.HOST_LAPI + 'login'
    data = {'userphone': userphone,
            'password': CT.to_md5(password),
            'phonecode': 86,
            'type': 1
            }
    response = requests.post(url=base_url, data=data)
    token = response.json()["data"]["token"]
    return token

token_stu = get_stu_token(Environment.userphone_stu, Environment.password_stu)

def getOrderType():  # !問題就出在這裡,在非測試函式的地方引用fixture並不會生效,但當時不知道
    url = Environment.HOST_LAPI + 'getOrderType'
    data = {
        'token':token_stu,
    }
    resp = requests.post(url=url,data=data)
    return resp.json()['data']  # 會返回[0,1,2,3,4]

def data_orderList():
    total = []

    orderType_list = getOrderType()
    submitState_list = [None,1,2]

    for i in range(5):
        item = []
        case = {}
        case['OrderType'] = orderType_list[i % len(orderType_list)]
        case['submitState'] = submitState_list[i % len(submitState_list)]

        item.append(case)

        expect = {}
        expect['code'] = 2000
        item.append(expect)

        item.append('正常入參')

        total.append(item)

    # token為空
    total.append([{'OrderType': 0,'submitState': None}, {'code': 4001}, 'token為空'])

    return total

執行後可以正常執行,但我當時心中又有一個疑問,我在造數指令碼里呼叫登入介面,會不會導致測試指令碼里的 token 失效?經過實踐後得到結論:在造數函式中呼叫登入介面並不會致使測試指令碼中的 token 失效,原因是pytest 在執行測試時會先進行測試用例的收集,然後再執行測試,放在當前情景中,如果有影響,也是測試用例中呼叫登入介面後會致使造數指令碼中的 token 失效,可造數介面已經提供完價值了,token 失不失效也沒有任何影響了。

文中提到的引數化用的是@pytest.mark.parametrize,可能由於編輯器問題,釋出後變成了@user5ize,所以我在 @ 後增加了一個空格

相關文章