例如有一个订单查询接口 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,所以我在 @ 后增加了一个空格