领取资料,咨询答疑,请➕wei: June__Go
上一小节中我们学习了pytest用例前后置方法的使用,本小节我们讲解一下pytest用例的参数化方法。
参数化简介:
参数化测试是指在测试用例中通过传入不同的参数来运行多次测试,以验证被测函数或方法的不同输入输出。其实也就是数据驱动测试的概念。在 unittest 中,使用ddt库配合unittest实现数据驱动。在pytest中并不需要额外的库,通过pytest.mark.parametrize()即可实现参数化。总之,pytest参数化使得我们可以方便地对测试用例进行扩展,减少了冗余代码,提高了测试的效率。
pytest有以下几种传参方式:
- @pytest.mark.parametrize() 通过装饰器方式进行参数化(最常使用)
- pytest.fixture()方式进行参数化,fixture装饰的函数可以作为参数传入其他函数
- conftest.py文件中存放参数化函数,可作用于模块内的所有测试用例
- 使用pytest的钩子函数pytest_generate_tests来自定义参数化测试的生成方式
- 使用pytest-data库:pytest-data是一个用于参数化测试的扩展库,可以通过加载外部数据文件来提供参数化测试的数据
- 使用自定义参数化装饰器:除了pytest提供的装饰器,还可以自定义参数化装饰器来实现特定的参数化方式
使用parametrize进行参数化
@pytest.mark.parametrize() 装饰器接收两个参数,第一个参数是以字符串的形式标识用例函数的参数,第二个参数以列表或元组的形式传递测试数据。
@pytest.mark.parametrize('参数化名称',参数化值) 该方法可以作用测试类和测试函数中
1、单个参数(data数据既可以是列表也可以是元组)
data为单一列表
import pytest
data=["lucy","lisa"]
@pytest.mark.parametrize('name',data)
def test_params(name):
print(f'测试数据是{name}')
data为单一元组
import pytest
data=("lucy","lisa")
@pytest.mark.parametrize('name',data)
def test_params(name):
print(f'测试数据是{name}')
data为列表里嵌套列表
import pytest
data = [
[1,1,2],
[2,2,4],
[3,3,6],
[4,4,8]
]
@pytest.mark.parametrize("test_data",data)
def test_login(test_data):
print(f"username is {test_data}")
data为列表里嵌套元组
import pytest
data = [
(1,1,2),
(2,2,4),
(3,3,6),
(4,4,8)
]
@pytest.mark.parametrize("test_data",data)
def test_login(test_data):
print(f"username is {test_data}")
data为列表里嵌套字典
import pytest
data = [{"user": "admin", "password": "123456"},
{"user": "super", "password": "654321"},
{"user": "sysadmin", "password": "321456"}
]
@pytest.mark.parametrize("test_data",data)
def test_login(test_data):
print(f"username is {test_data['user']}\n password is {test_data['password']}")
2、多个参数
data为列表里嵌套列表
import pytest
data = [
[1,1,2],
[2,2,4],
[3,3,6],
[4,4,8]
]
@pytest.mark.parametrize("a,b,c",data)
def test_login(a,b,c):
print(f"\na,b,c is: {a},{b},{c}")
data为列表里嵌套元组
import pytest
data = [
(1, 1, 2),
(2, 2, 4),
(3, 3, 6),
(4, 4, 8)
]
@pytest.mark.parametrize("a,b,c", data)
def test_login(a, b, c):
print(f"\na,b,c is: {a},{b},{c}")
3、多个parametrize参数叠加(结果种类为多个参数相乘)
在实际测试中,有的场景多条件查询,比如登录有2个条件,名字有两种密码有四种情况,如果要全部覆盖,则是2*4==8种情况。这种情景,人工测试一般是不会全部覆盖的,但在自动化测试中,只要你想,就可以做到。如下示例:
import pytest
class TestAdd():
@pytest.mark.parametrize('pwd', [33,None,44, 55])
@pytest.mark.parametrize('name', [11, 22,])
def test_add1(self, name, pwd):
print(f'name:{name} pwd:{pwd}')
运行结果:2*4==8种情况
collected 8 items
test_demo.py::TestAdd::test_add1[11-33] PASSED [1/8]
test_demo.py::TestAdd::test_add1[11-None] PASSED [2/8]
test_demo.py::TestAdd::test_add1[11-44] PASSED [3/8]
test_demo.py::TestAdd::test_add1[11-55] PASSED [4/8]
test_demo.py::TestAdd::test_add1[22-33] PASSED [5/8]
test_demo.py::TestAdd::test_add1[22-None] PASSED [6/8]
test_demo.py::TestAdd::test_add1[22-44] PASSED [7/8]
test_demo.py::TestAdd::test_add1[22-55] PASSED
4.1、ids 自定义测试id
通过上面的运行结果,我们可以看到,为了区分参数化的运行结果,在结果中都会显示数据组合而成的名称。
测试结果会自动生成测试id,自动生成的id短小还好说,如果数据比较长而复杂的话,那么就会很难看。
@pytest.mark.parametrize() 提供了 ids 参数来自定义显示结果,就是为了好看易读。
import pytest
class TestAdd():
@pytest.mark.parametrize('name,pwd', [(10,11),(20,21),(30,31)],ids=(['zhangsan','lisi','wangmazi']))
def test_add1(self, name, pwd):
print(f'name:{name} pwd:{pwd}')
运行结果:以zhangsan、zhangsan、wangmazi显示
collected 3 items
test_demo.py::TestAdd::test_add1[zhangsan] PASSED [1/3]
test_demo.py::TestAdd::test_add1[zhangsan] PASSED [2/3]
test_demo.py::TestAdd::test_add1[wangmazi] PASSED
4.2 pytest.param 自定义测试id
在参数化测试中,每个测试用例可能包含多组参数,并且可能会产生大量的测试结果。这时,为了更好地理解和调试测试结果,给每个参数化测试用例指定一个易于理解的标识是很有意义的。而 pytest.param 函数的 id 参数就能做到这一点。
import pytest
@pytest.mark.parametrize("input,expected", [pytest.param(2, 4, id="case1"),
pytest.param(3, 9, id="case2"),
pytest.param(5, 25, id="case3")])
def test_multiply(input, expected):
assert input * input == expected
运行结果:以case1、case2、case3显示
============================= test session starts =============================
collecting ... collected 3 items
test_demo.py::test_multiply[case1] PASSED [ 33%]
test_demo.py::test_multiply[case2] PASSED [ 66%]
test_demo.py::test_multiply[case3] PASSED [100%]
============================== 3 passed in 0.03s ==============================
4.3 pytest.param自定义选项
除了测试id参数,pytest.param 函数还可以接受额外的参数,例如 marks 参数,用于为单个测试用例应用自定义标记。通过使用 marks 参数,我们可以在参数化测试中灵活地添加各种自定义选项。
import pytest
@pytest.mark.parametrize("input,expected", [pytest.param(2, 4, id="case1"),
pytest.param(3, 10, id="case2", marks=pytest.mark.xfail),
pytest.param(5, 25, id="case3", marks=pytest.mark.skip)])
def test_multiply(input, expected):
assert input * input == expected
运行结果:
test_demo.py::test_multiply[case1]
test_demo.py::test_multiply[case2]
test_demo.py::test_multiply[case3]
=================== 1 passed, 1 skipped, 1 xfailed in 0.25s ===================
PASSED [ 33%]XFAIL [ 66%]
input = 3, expected = 10
@pytest.mark.parametrize("input,expected", [pytest.param(2, 4, id="case1"),
pytest.param(3, 10, id="case2", marks=pytest.mark.xfail),
pytest.param(5, 25, id="case3",marks=pytest.mark.skip)])
def test_multiply(input, expected):
> assert input * input == expected
E assert (3 * 3) == 10
在上述示例中,我们使用 pytest.mark.skip, pytest.mark.xfail标记这两个测试用例。通过这种方式,我们可以对不同的参数化测试用例应用不同的标记,以实现更加灵活的测试控制 。
5、使用CSV或Excel文件作为测试数据进行参数化
可以使用Python的csv或openpyxl库来读取文件并生成参数组合。以下是一个示例:
import pytest
import csv
def read_csv(file_path):
with open(file_path, 'r') as csv_file:
csv_reader = csv.DictReader(csv_file)
for row in csv_reader:
yield row
@pytest.mark.parametrize("data", read_csv("test_data.csv"))
def test_multiply(data):
num1 = int(data['num1'])
num2 = int(data['num2'])
expected = int(data['expected'])
assert num1 * num2 == expected
使用pytest.fixture进行参数化
上面介绍了pytest中的自带的参数化方法,我们也可以通过使用fixture中的params参数来做参数化。
request.param:用于获取测试的请求参数。【获取测试上下文的信息】
①【注意】fixture函数的 params 请求参数数量(请求参数的数据类型为列表/元组,请求参数数量为列表/元组元素个数)决定fixture函数执行的次数。
②【注意】此时fixture函数的装饰器 @pytest.fixture(params=get_data) 参数不能忘记传值。
1、单个参数
import pytest
data3 = [{"user": "admin", "password": "123456"},
{"user": "super", "password": "654321"},
{"user": "sysadmin", "password": "321456"}
]
@pytest.fixture(params=data3,autouse=True,scope="class")
def get_data(request):
print("fixture begin")
yield request.param
print("fixture end")
class TestLogin:
def test_login(self,get_data):
test_data = get_data
print("username is {} AND password is {}".format(test_data["user"], test_data["password"]))
2、多个参数
import pytest
#此处是列表嵌套元祖
data1 = [('admin', '12346'), ("super", "654321"), ("sysadmin", '321456')]
data2 = [('admin', '12346'), ("super", "654321"), ("sysadmin", '321456')]
@pytest.fixture(params=data1,autouse=True,scope="class")
def get_data_1(request):
print("fixture begin")
yield request.param
print("fixture end")
@pytest.fixture(params=data2,autouse=True,scope="class")
def get_data_2(request):
print("fixture begin")
yield request.param
print("fixture end")
class TestLogin:
def test_login(self,get_data_1,get_data_2):
test_data_1 = get_data_1
test_data_2 = get_data_2
print("username is {};password is {}".format(get_data_1, get_data_2))
3、pytest.fixture与parametrize结合一起使用
fixture自身的params参数可以结合request来传参,当然也可以用parametrize来参数化代替params
如果测试方法写在类中,则@pytest.mark.parametrize的参数名称要与@pytest.fixture函数名称保持一致
单个参数
import pytest
seq = [1, 2, 3]
@pytest.fixture()
def ss_data(request):
print("\n参数 %s" % request.param)
return request.param + 1
class TestData:
@pytest.mark.parametrize("ss_data", seq, indirect=True)
def test_1(self, ss_data):
print("用例", ss_data)
多个fixture和多个parametrize叠加
import pytest
seq1 = [1, 2, 3]
seq2 = [4, 5, 6]
@pytest.fixture()
def get_seq1(request):
seq1 = request.param
print("seq1:", seq1)
return seq1
@pytest.fixture()
def get_seq2(request):
seq2 = request.param
print("seq2:", seq2)
return seq2
@pytest.mark.parametrize("get_seq1", seq1, indirect=True)
@pytest.mark.parametrize("get_seq2", seq2, indirect=True)
def test_1(get_seq1, get_seq2):
print(get_seq1, 11)
print(get_seq2, 22)
使用conftest.py进行参数化
conftest.py特点:
- conftest.py 文件中存放参数化函数,可作用于模块内的所有测试用例
- conftest.py 配置里可以实现数据共享,不需要import就能自动找到一些配置,pytest默认读取里面的配置
conftest.py配置需要注意以下点:
- conftest.py配置脚本名称是固定的,不能改名称
- conftest.py与运行的用例要在同一个pakage下,并且有__init__.py文件
- 不需要import导入 conftest.py,pytest用例会自动查找
conftest.py应用场景
1、每个接口需共用到的token
conftest.py
import pytest
@pytest.fixture(scope='session')
def get_token():
token = 'qeehfjejwjwjej11sss112'
return token
test_params.py
def test_get_token(get_token):
token = get_token
print(token)
2、每个接口需共用到的测试用例数据
编写一个fixture在conftest.py,内容如下
data = [["admin","123456"],["superadmin","654321"]]
@pytest.fixture(scope='session',params=data)
def get_test_data(request):
yield request.param
在测试脚本里面使用,只需要引入即可,新建一个测试文件test_params.py,内容如下
def test_get_data(get_test_data):
print(f"user is {get_test_data[0]} and pwd is {get_test_data[1]}")
3、每个接口需共用到的配置信息
编写一个fixture在conftest.py,内容如下
@pytest.fixture(scope='session')
def get_base_url():
base_url = "http://www.baidu.com"
return base_url
在测试脚本里面使用,只需要引入即可,新建一个测试文件test_params.py,内容如下
def test_base_url(get_base_url):
url = get_base_url
print(url)
使用pytest_generate_tests钩子函数参数化
可以使用pytest的钩子函数pytest_generate_tests来自定义参数化测试的生成方式。下面是一个示例:
import pytest
def pytest_generate_tests(metafunc):
if 'num' in metafunc.fixturenames:
metafunc.parametrize('num', [1, 2, 3])
def test_square(num):
assert num ** 2 == num * num
在上面的示例中,定义了一个pytest_generate_tests钩子函数,通过判断测试函数的参数是否存在来进行参数化。每个参数组合都会作为单独的测试用例执行。
使用pytest-data库参数化
pytest-data是一个用于参数化测试的扩展库,可以通过加载外部数据文件来提供参数化测试的数据。以下是一个示例:
import pytest
from pytest_data import data
@pytest.mark.datafiles('test_data.csv')
def test_addition(datafiles):
data_file = datafiles / 'test_data.csv'
for row in data(data_file):
num1 = row['num1']
num2 = row['num2']
expected = row['expected']
assert num1 + num2 == expected
在上面的示例中,使用pytest-data库的data装饰器加载了一个CSV文件作为测试数据,并在测试函数中使用了这些数据进行参数化测试。
使用自定义参数化装饰器进行参数化
除了pytest提供的装饰器,还可以自定义参数化装饰器来实现特定的参数化方式。示例如下
import pytest
def custom_parametrize(*args):
def decorator(func):
for arg in args:
func = pytest.mark.parametrize(*arg)(func)
return func
return decorator
@custom_parametrize(
("num", [1, 2, 3]),
("operation", ["add", "subtract"])
)
def test_calculator(num, operation):
if operation == "add":
result = num + num
assert result == 2 * num
elif operation == "subtract":
result = num - num
assert result == 0
在上面的示例中,定义了一个自定义的参数化装饰器custom_parametrize,接受一系列参数化参数,并将其应用于测试函数。使用自定义装饰器可以实现更复杂的参数化逻辑。
最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走,希望可以帮助到大家!领取资料,咨询答疑,请➕wei: June__Go