Pytest参数化-下篇
😎😎原文出自:测个der,博主的公众号,格式美观一些。
关于参数化的其他案例
数据嵌套及多参数传参
import pytest
pwd_datas = [["QINGAN",{"user":"SHIER","pwd":"123456"}],["SHIER",{"pwd":"654321","user":"QINGAN"}]]
class Test_severity:
@pytest.mark.parametrize("user,datas",pwd_datas)
def test_LOGIN(self,user,datas):
print(user)
print(datas)
"""
test_browser.py::Test_severity::test_LOGIN[QINGAN-datas0] QINGAN
{'user': 'SHIER', 'pwd': '123456'}
PASSED
test_browser.py::Test_severity::test_LOGIN[SHIER-datas1] SHIER
{'pwd': '654321', 'user': 'QINGAN'}
PASSED
"""
❝在后续的实战中 ,此处是最为常用的部分,不论是两个参数还是三个四个参数,都可以采用这样的方式。比如登录接口要做数据处理的操作的时候,就可以这样写,账号密码,以及备注,接口地址等,都可以直接用数据组合的方式集成到测试用例里面来。
❞
示例
import pytest
pwd_datas = [
["QINGAN", 'login', {"user": "SHIER", "pwd": "123456"}],
["SHIER", 'login1', {"pwd": "654321", "user": "QINGAN"}]
]
class Test_severity:
@pytest.mark.parametrize("user,text,datas", pwd_datas)
def test_LOGIN(self, user, text,datas):
print(user)
print(datas)
print(text)
"""
test_browser.py::Test_severity::test_LOGIN[QINGAN-login-datas0] QINGAN
{'user': 'SHIER', 'pwd': '123456'}
login
PASSED
test_browser.py::Test_severity::test_LOGIN[SHIER-login1-datas1] SHIER
{'pwd': '654321', 'user': 'QINGAN'}
login1
PASSED
"""
此处具体体现在这可能看着不是很明显,如果有一定的底子,此处还是可以看出用意的。可以将user看做账号,text看做备注信息。其他的自然就是账号密码了。可以参照后续的测试报告,可以更直观的看到测试数据的展示。
HOOK自定义参数化
自定义传参
此处都是重写并嵌入自己写的Pytest的一些方法。pytest_generate_tests方法。先看看官网的写法:
"""conftest.py"""
def pytest_addoption(parser):
parser.addoption("--all", action="store_true", help="run all combinations")
def pytest_generate_tests(metafunc):
if "param1" in metafunc.fixturenames:
if metafunc.config.getoption("all"):
end = 5
else:
end = 2
metafunc.parametrize("param1", range(end))
"""test_a.py"""
def test_compute(param1):
assert param1 < 4
$ pytest -q --all
....F [100%]
================================= FAILURES =================================
_____________________________ test_compute[4] ______________________________
param1 = 4
def test_compute(param1):
> assert param1 < 4
E assert 4 < 4
test_compute.py:4: AssertionError
========================= short test summary info ==========================
FAILED test_compute.py::test_compute[4] - assert 4 < 4
1 failed, 4 passed in 0.12s
没看懂是吧。 首先需要说明的一点就是:命令行执行确实可以。但是写入到pytest配置文件就不行了。原因是你的conftest.py文件不是全局文件。 解决方案:将conftest.py挪动至你的配置文件同级目录即可。记得在addopts 参数处加上--all
解析
"""conftest.py"""
def pytest_addoption(parser):
parser.addoption("--stringinput", action="store",help="run all combinations")
def pytest_generate_tests(metafunc):
if "stringinput" in metafunc.fixturenames:
metafunc.parametrize("stringinput", metafunc.config.getoption("stringinput"))
"""test_a.py"""
def test_compute(stringinput):
print("第", stringinput,"个清安")
❝如果你是命令行运行,记得将配置文件的addopts = --stringinput去除,反之加上。 pytest_addoption类似于注册功能。注册一个命令参数。action类似于标记。还有一个 default 参数,help帮助提示。 metafunc.parametrize类似于返回值,metafunc.config.getoption("stringinput")也就是那个值了。括号中的字符串stringinput,也就相当于引用了,可以理解为变量。便于另一端的test使用
❞
关于action参数,了解一下:
1. 'store'- 这只是存储参数的值。这是默认操作。例如:
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--foo')
2. 'store_const'- 这存储由 const 关键字参数指定的值。该'store_const'操作最常与指定某种标志的可选参数一起使用。例如:
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--foo', action='store_const', const=42)
3. 'store_true'和'store_false'- 这些是 'store_const'用于分别存储值 True 和的特殊情况 False 。此外,它们分别创建 False 和 的默认值 True。例如:
>>> parser = argparse.ArgumentParser()
>>>parser.add_argument('--foo', action='store_true')
>>>parser.add_argument('--bar', action='store_false')
4. 'append'- 这存储了一个列表,并将每个参数值附加到列表中。这对于允许多次指定选项很有用。示例用法:
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--foo', action='append')
5. 'append_const'- 这存储了一个列表,并将 const 关键字参数指定的值附加到列表中(请注意,const 关键字参数默认为 None)。'append_const'当多个
参数需要将常量存储到同一个列表时,该操作通常很有用。例如:
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--str', dest='types', action='append_const', const=str)
>>> parser.add_argument('--int', dest='types', action='append_const', const=int
6. 'count'- 这会计算关键字参数出现的次数。例如,这对于提高详细程度很有用:
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--verbose', '-v', action='count', default=0)
钩子函数本就不常用,因为pytest本身提供的方法已经可以满足我们绝大部分的需求了,所以我们目前只需要了解到此处的部分用法即可。 此外,上述例子中我们可以通过控制台或者配置文件传递多个参数:--stringinput=1 --stringinput=2
示例-action = append
"""conftest.py"""
def pytest_addoption(parser):
parser.addoption("--stringinput", action="append",default=['1','2','3'],help="run all combinations")
def pytest_generate_tests(metafunc):
if "stringinput" in metafunc.fixturenames:
metafunc.parametrize("stringinput", metafunc.config.getoption("stringinput"))
def test_compute(stringinput):
print("第", stringinput,"个清安")
注意此处我把配置文件的注册参数--stringinput已经删除,default参数内容也改了。
Case/test_a.py::test_compute[1] 第 1 个清安
PASSED
Case/test_a.py::test_compute[2] 第 2 个清安
PASSED
Case/test_a.py::test_compute[3] 第 3 个清安
PASSED
关于此处用法太多了,不一一举例展示,举一反三。获取可能都用不上自定义参数。
自定义传参2-引用数据
"""conftest.py"""
def pytest_addoption(parser):
parser.addoption("--user", action="store_true",help="run all combinations")
def pytest_generate_tests(metafunc):
if "user" in metafunc.fixturenames:
if metafunc.config.getoption("user"):
metafunc.parametrize("user", metafunc.module.users,
ids=metafunc.module.names,
scope='function')
"""test_a.py"""
names = [3, 2, 1]
users = ["清安","拾贰","北海"]
def test_compute(user):
print("你的名字是:",user)
❝此处需要在配置文件中加入--user。否则运行异常。
❞
自定义传参3-数据引用加判断
"""conftest.py"""
def pytest_addoption(parser):
parser.addoption("--user", action="store_true",default='look',help="run all combinations")
def pytest_generate_tests(metafunc):
if "data" in metafunc.fixturenames:
con = metafunc.config.getoption("user")
if con == 'look':
metafunc.parametrize("data", metafunc.module.users,
ids=metafunc.module.names,
scope='function')
names = [3, 2, 1]
users = ["清安","拾贰","北海"]
@pytest.fixture
def data(request):
return request.param
def test_compute(data):
print("你的名字是:",data)
"""
Case/test_a.py::test_compute[3] 你的名字是: 清安
PASSED
Case/test_a.py::test_compute[2] 你的名字是: 拾贰
PASSED
Case/test_a.py::test_compute[1] 你的名字是: 北海
PASSED
"""
如果不写一个fixture用于传参,会报错哦,所以此处加了一个。配置里面的参数中可以不写--user。此外注意pytest_generate_tests中的参数是有变动的。与前两个例子不同。
场景化参数
跟上述所讲的自定义传参类似,区别不大,就是在函数方法中做了一些数据处理。
def pytest_generate_tests(metafunc):
idlist = []
argvalues = []
for scenario in metafunc.cls.scenarios:
idlist.append(scenario[0])
items = scenario[1].items()
argnames = [x[0] for x in items]
argvalues.append([x[1] for x in items])
metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class")
scenario1 = ("basic", {"attribute": "value"})
scenario2 = ("advanced", {"attribute": "value2"})
class TestSampleWithScenarios:
scenarios = [scenario1, scenario2]
def test_demo1(self, attribute):
print(attribute)
assert isinstance(attribute, str)
def test_demo2(self, attribute):
print(attribute)
assert isinstance(attribute, str)
这是来自官网的例子,了解即可。 pytest会自动执行pytest_generate_tests,将元组的值取出放入列表,再将元组中字典的值取出,放入列表,通过metafunc.parametrize返回,在测试函数中通过形参的方式接收。 注意:metafunc.cls.scenarios是一个列表,在TestSampleWithScenarios类中。如果解析后还是不清楚,推荐另一种方法:加打印。
def pytest_generate_tests(metafunc):
idlist = []
argvalues = []
for scenario in metafunc.cls.scenarios:
print("scenario",scenario)
idlist.append(scenario[0])
items = scenario[1].items()
print(1,items)
argnames = [x[0] for x in items]
print(2,argnames)
argvalues.append([x[1] for x in items])
print(idlist)
print(argvalues)
metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class")
这样就能知道每一步具体的值了。便于分析。打断点也是不错的方法。
类配置化参数
import pytest
def pytest_generate_tests(metafunc):
# called once per each test function
funcarglist = metafunc.cls.params[metafunc.function.__name__]
print(1,funcarglist)
argnames = sorted(funcarglist[0])
print(2,argnames)
metafunc.parametrize(
argnames, [[funcargs[name] for name in argnames] for funcargs in funcarglist]
)
class TestClass:
# a map specifying multiple argument sets for a test method
params = {
"test_equals": [dict(a=1, b=2), dict(a=3, b=3)],
"test_zerodivision": [dict(a=1, b=0)],
}
def test_equals(self, a, b):
print(a,b)
assert a == b
def test_zerodivision(self, a, b):
with pytest.raises(ZeroDivisionError):
a / b
这也是一个来自官网的例子,看着复杂巴拉的,其实是真麻烦。所幸用的少,不想看的可以直接略过!
❝funcarglist拿到的就是TestClass中的params值,argnames对它进行一个简单的排序,在metafunc.parametrize中做具体的处理,列表推导式。先for funcargs in funcarglist循环出列表中的字典,再for name in argnames循环出列表中的元素,并以字典取值[funcargs[name]的方式将值拿出来,进行返回
❞
最后就是传值的过程了,在程序的后端已经将返回值应该给到哪个测试用例已经处理好了。
文件读取
yaml读取参数
yaml用例:
-
urls:
CSDN: https://blog.csdn.net/weixin_52040868
import yaml
import pytest
"""读取Yaml文件"""
def ReadYaml(path):
with open(path, encoding='utf8') as r:
data = yaml.load(r, Loader=yaml.FullLoader)
return data
"""参数化使用"""
@pytest.mark.parametrize('data',ReadYaml('./Case/case.yaml'))
def test_yaml(data):
print(data)
"""
Case/test_a.py::test_yaml[data0] {'urls': {'CSDN': 'https://blog.csdn.net/weixin_52040868'}}
PASSED
"""
此处是简单的举例了使用步骤。Yaml的用例写法以及内容可以根据实际情况进行设定可以是列表嵌套字典。当然也可以是上述的情况,字典嵌套字典。
Excel读取参数
image.png
import pytest
import openpyxl
def ReadExcel(path):
book = openpyxl.load_workbook(path)
sheet = book.active
case = sheet['A2':'B2']
for i,j in case:
return (i.value,j.value)
@pytest.mark.parametrize('data',ReadExcel(r"D:\python\pycase.xlsx"))
def test01(data):
print(data)
文件读取多参数
很多人在写测试框架的时候,喜欢用pytest的parametrize的时候喜欢多参数传递。怎么操作的,以yaml为例子:
-
-
url1:
CSDN: https://blog.csdn.net/weixin_52040868
-
URL2:
GZH: HTTPS://
我随意修改了一下yaml的结构。使其处于一个列表中。
import yaml
import pytest
def ReadYaml(path):
with open(path, encoding='utf8') as r:
data = yaml.load(r, Loader=yaml.FullLoader)
return data
@pytest.mark.parametrize('da,ta',ReadYaml('./Case/case.yaml'))
def test_yaml(da,ta):
print(da,ta)
"""
Case/test_a.py::test_yaml[da0-ta0] {'url1': {'CSDN': 'https://blog.csdn.net/weixin_52040868'}} {'URL2': {'GZH': 'HTTPS://'}}
PASSED
"""
这样就能得到两个字典了(原数据结构式列表嵌套字典),也能直接进行字典取值了。具体如何运用,主要还是看如何组建参数。在本章中,讲解了多参数传递的例子,与此结合起来巩固巩固。