一、pytest实现测试用例参数化(@pytest.mark.parametrize) 

  @pytest.mark. parametrize装饰器可以实现对测试用例的参数化,方便测试数据的获取。

@pytest.mark. parametrize的基本使用:

方便测试函数对测试数据的获取。

方法:
parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)

参数说明:

pytest实现用例参数化(@pytest.mark.parametrize)_用例

参数化用法

1、单个参数【测试方法入参只有一个参数】

# file_name: test_parametrize.py


import pytest


class Test_D:

@pytest.mark.parametrize("a", [1, 2, 3]) # 参数a被赋予3个值,test_a将会运行3遍
def test_a(self, a): # 参数必须和parametrize里面的参数一致
print('\n------------------> test_a has ran, and a = {}'.format(a))
assert 1 == a

if __name__ == '__main__':
pytest.main(['-s', 'test_parametrize.py'])

运行结果:

pytest实现用例参数化(@pytest.mark.parametrize)_测试用例_02

 从运行结果中可以看到test_a方法被执行了3遍,说明参数a参数化成功。

2、多个参数【测试方法入参有多个参数】

# file_name:test_parametrize.py


import pytest


class Test_D:

@pytest.mark.parametrize("a,b", [(1, 2), (2, 3), (3, 4)]) # 参数a,b均被赋予三个值,函数会运行三遍
def test_b(self, a, b): # 参数必须和parametrize里面的参数一致
print('\n------------------> test_b has ran, and a = {}, b = {}'.format(a, b))
assert 1

if __name__ == '__main__':
pytest.main(['-s', 'test_parametrize.py'])

运行结果:

pytest实现用例参数化(@pytest.mark.parametrize)_用例_03

从运行结果中可以看到test_b方法被执行了三遍,说明参数a,b都已经被参数化。

3、利用函数的返回值进行用例参数化

# file_name: test_parametrize.py


import pytest

# 定义返回参数值的函数
def return_test_data():
return [(1, 2), (2, 3), (3, 4)]


class Test_D:

@pytest.mark.parametrize("a,b", return_test_data()) # 使用函数返回值的方式传入参数值
def test_c(self, a, b):
print('\n------------------> test_c has ran, and a = {}, b = {}'.format(a, b))
assert 1

if __name__ == '__main__':
pytest.main(['-s', 'test_parametrize.py'])

运行结果:

pytest实现用例参数化(@pytest.mark.parametrize)_参数化_04

从运行结果中可以看到test_c方法被执行了三遍,这说明使用函数的返回值方式同样可以做到参数化。

4、参数组合

获得多个参数化参数的所有组合,可以堆叠使用参数化装饰器:【每一个参数化装饰器代表参数化测试方法中的一组测试数据】

# file_name: test_parametrize.py


import pytest


class Test_D:

@pytest.mark.parametrize("x", [1, 2])
@pytest.mark.parametrize("y", [3, 4])
def test_d(self, x, y):
print("\n------------------> test_d has ran, and x={}, y={}".format(x, y))

if __name__ == '__main__':
pytest.main(['-s', 'test_parametrize.py'])

运行结果:

pytest实现用例参数化(@pytest.mark.parametrize)_用例_05

从运行结果中可以看到test_d方法被执行了四遍,这是因为参数x和参数y分别被赋予了2个值,组合起来就是2*2=4次,说明装饰器叠加可以获得所有参数的组合。 

二、参数化@pytest.mark.parametrize装饰器中的indirect参数使用

① indirect 参数一般与pytest中的fixture函数中的 request 组合使用。

②当 indrect=True 时,argnames则要传入fixture函数名称,不再是一个普通参数,而是要被调用的fixture函数,argvalues则是要给这个fixture函数传的值。

③用法其实与 @pytest.fixture(params) 一样,但使用了 @pytest.mark.parametrize 相当于参数化了fixture函数,而不是只有固定的一套数据传入使用。

实例1:【因为参数化装饰器的参数 indirect=Ture ,所以pytest测试框架将login_r当作函数执行,且将test_user_data列表作为参数传入到login_r函数中】

import pytest

test_user_data = ['Tom', 'Jerry']


# 方法名作为参数
@pytest.fixture(scope='module')
def login_r(request):
user = request.param # 通过 request.param 获取测试参数
print(f" 登录用户: {user}")
return user


@pytest.mark.parametrize("login_r", test_user_data, indirect=True)
def test_login(login_r):
a = login_r
print(f"测试用例中 login_r 函数 的返回值; {a}")
assert a != ""

运行结果:

pytest实现用例参数化(@pytest.mark.parametrize)_测试用例_06

实例2:单fixture单值(通过列表)

import pytest


@pytest.fixture()
def login(request):
user = request.param
print("传入的用户名为:{}".format(user))
return user


user = ['张三', '李四']


@pytest.mark.parametrize('login', user, indirect=True) # 此时测试函数参数化的值为login fixture函数的返回值(参数列表user中存在几组数据就进行了几次参数化)
def test_one_param(login):
print("测试函数的读到的用户是:{}".format(login))

运行结果:

pytest实现用例参数化(@pytest.mark.parametrize)_用例_07

 详细解释:

pytest实现用例参数化(@pytest.mark.parametrize)_测试用例_08

整个调用过程如下:

pytest实现用例参数化(@pytest.mark.parametrize)_参数化_09

实例3:单fixture多值(通过字典)

import pytest

user_info = [
{'user': '张三', 'pwd': 123},
{'user': '李四', 'pwd': 456}
]


@pytest.fixture()
def login(request):
user = request.param
print("传入的用户名为:{},密码为:{}".format(user['user'], user['pwd']))
return user


@pytest.mark.parametrize('login', user_info, indirect=True)
def test_one_param(login):
print("测试类的读到的用户是:{} 密码是:{}".format(login['user'], login['pwd']))

运行结果:

pytest实现用例参数化(@pytest.mark.parametrize)_用例_10

实例4:传多fixture多值(通过嵌套元组的列表)

import pytest

# 一个@pytest.mark.parametrize使用多个fixture,传入的数据要是嵌套了元组的列表
user_info = [
('张三', 123),
('李四', 'pwd')
]


@pytest.fixture()
def login_user(request):
user = request.param
print("传入的用户名为:{}".format(user))
return user


@pytest.fixture()
def login_pwd(request):
pwd = request.param
print("密码为:{}".format(pwd))
return pwd


@pytest.mark.parametrize('login_user,login_pwd', user_info, indirect=True)
def test_one_param(login_user, login_pwd):
print("测试类的读到的用户是:{} 密码是:{}".format(login_user, login_pwd))

运行结果:

pytest实现用例参数化(@pytest.mark.parametrize)_用例_11

实例5:叠加fixture(单值列表,执行次数笛卡尔集 N*M)

import pytest

user = ['张三', '李四']
pwd = [124, 345]


@pytest.fixture()
def login_user(request):
user = request.param
print("传入的用户名为:{}".format(user))
return user


@pytest.fixture()
def login_pwd(request):
pwd = request.param
print("密码为:{}".format(pwd))
return pwd


@pytest.mark.parametrize('login_pwd', pwd, indirect=True)
@pytest.mark.parametrize('login_user', user, indirect=True)
def test_one_param(login_user, login_pwd):
print("测试类的读到的用户是:{} 密码是:{}".format(login_user, login_pwd))

运行结果:

pytest实现用例参数化(@pytest.mark.parametrize)_测试用例_12

三、参数化@pytest.mark.parametrize装饰器中的scope参数使用

① scope 参数的作用范围取值与fixture函数的scope一致,且hi有当 indirect=True 才会被使用。

② scope 参数的作用范围会覆盖fixture函数的scope范围,如果同一个被调用的fixture函数有多个parametrize定义了scope,取第一条的范围。

实例:

import pytest


@pytest.fixture(scope='class')
def login_user(request):
user = request.param
print("传入的用户名为:{}".format(user))
return user


@pytest.fixture(scope='class')
def login_pwd(request):
pwd = request.param
print("密码为:{}".format(pwd))
return pwd


class TestCase:
userinfo = [
('张三', 123)
]
ids = ["case{}".format(i) for i in range(len(userinfo))]

@pytest.mark.parametrize('login_user,login_pwd', userinfo, ids=ids, indirect=True, scope='function')
def test_one_param(self, login_user, login_pwd):
print("测试类的读到的内容是{}{}".format(login_user, login_pwd))

@pytest.mark.parametrize('login_user,login_pwd', userinfo, ids=ids, indirect=True, scope='function')
def test_one_param2(self, login_user, login_pwd):
print("测试类的读到的内容是{}{}".format(login_user, login_pwd))

运行结果:

①小于fixture函数的scope范围:

pytest实现用例参数化(@pytest.mark.parametrize)_参数化_13

②大于fixture函数的scope范围:

pytest实现用例参数化(@pytest.mark.parametrize)_参数化_14

③多个SCOPE范围为先执行的:

pytest实现用例参数化(@pytest.mark.parametrize)_参数化_15

与③相比调整一下顺序:

pytest实现用例参数化(@pytest.mark.parametrize)_测试用例_16

四、参数化@pytest.mark.parametrize加mark标记跳过执行其中一组或者多组测试数据(标记同一的测试方法中的子用例,并在子用例上做mark操作:xfail,skip等等)

需求:pytest 使用  @pytest.mark.parametrize 对测试用例进行参数化的时候,当存在多组测试数据时,需要对其中的一组或者多组测试数据加标记跳过执行,可以用 pytest.param 实现。

pytest.param用法:

参数:

① param values :按顺序传参数集值的变量args

② keyword marks  : marks关键字参数,要应用于此参数集的单个标记或标记列表。

③ keyword str id : id字符串关键字参数,测试用例的id属性【测试用例名】

源码:

def param(*values, **kw):
"""Specify a parameter in `pytest.mark.parametrize`_ calls or
:ref:`parametrized fixtures <fixture-parametrize-marks>`.

.. code-block:: python

@pytest.mark.parametrize("test_input,expected", [
("3+5", 8),
pytest.param("6*9", 42, marks=pytest.mark.xfail),
])
def test_eval(test_input, expected):
assert eval(test_input) == expected

:param values: variable args of the values of the parameter set, in order.
:keyword marks: a single mark or a list of marks to be applied to this parameter set.
:keyword str id: the id to attribute to this parameter set.
"""
return ParameterSet.param(*values, **kw)

实例1:xfail标记同一测试方法的子用例

import pytest

@pytest.mark.parametrize("test_input,expected", [
("3+5", 8),
pytest.param("6*9", 42, marks=pytest.mark.xfail),
])
def test_eval(test_input, expected):
assert eval(test_input) == expected

运行结果:

pytest实现用例参数化(@pytest.mark.parametrize)_测试用例_17

实例2:skip标记同一测试方法的子用例

import pytest


@pytest.mark.parametrize("user,psw",
[("student", "123456"),
("teacher", "abcdefg"),
pytest.param("admin", "我爱中华!", marks=pytest.mark.skip(reason='跳过原因'))
])
def test_login(user, psw):
print(user + " : " + psw)
assert 1 == 1

运行结果:

pytest实现用例参数化(@pytest.mark.parametrize)_测试用例_18

实例3:类比实例2与实例3子用例参数化的区别

import pytest


@pytest.mark.parametrize("number",
[pytest.param("1"),
pytest.param("2"),
pytest.param("3", marks=pytest.mark.skip(reason='该参数未准备好'))])
def test_login1(number):
print(number)
assert 1 == 1

运行结果:

pytest实现用例参数化(@pytest.mark.parametrize)_测试用例_19

实例4:

import pytest


@pytest.mark.parametrize("user,psw",
[pytest.param("admin1", "123456"),
pytest.param("admin2", "abcdefg"),
pytest.param("admin3", "higklmn", marks=pytest.mark.skip('此用户的登录信息还没有初始化,不可使用'))])
def test_login1(user, psw):
print(user + " : " + psw)
assert 1 == 1

运行结果:

pytest实现用例参数化(@pytest.mark.parametrize)_参数化_20

五、参数化@pytest.mark.parametrize装饰器中的id参数与ids参数使用

id参数需要参照ids参数并加以区分:pytest参数化自定义测试用例标题【@pytest.mark.parametrize(ids=XXX)】

①id参数是给用例添加标题内容,没加id参数的时候,用例会默认拿请求的参数当用例标题;如实例1

②id参数是指单条参数化测试用例数据时,分别为同一测试方法中的每一条测试用例进行命名;ids参数是指多条参数化测试用例数据时,ids参数传入一个列表或者元组,分别为同一测试方法中的所有测试用例进行命名。如实例2、实例3

实例1:

import pytest

@pytest.mark.parametrize("user,psw",
[pytest.param("admin1", "abcdefg"),
pytest.param("admin2", "123456"),
pytest.param("admin3", "qwerty", marks=pytest.mark.skip)])
def test_login1(user, psw):
print(user + " : " + psw)
assert 1 == 1

运行结果:

pytest实现用例参数化(@pytest.mark.parametrize)_参数化_21

实例2:

import pytest

@pytest.mark.parametrize("user,psw",
[pytest.param("admin1", "abcdefg", id="第一条测试用例名字"),
pytest.param("admin2", "123456", id="第二条测试用例名字"),
pytest.param("admin3", "qwerty", marks=pytest.mark.skip, id="第三条测试用例名字")])
def test_login1(user, psw):
print(user + " : " + psw)
assert 1 == 1

运行结果:

pytest实现用例参数化(@pytest.mark.parametrize)_用例_22

实例3:

import pytest


@pytest.mark.parametrize("user,psw", argvalues=[("admin1", "abcdefg"), ("admin2", "123456"), ("admin3", "qwerty")],
ids=["第一条测试用例名字", "第二条测试用例名字", "第三条测试用例名字"])
def test_login1(user, psw):
print(user + " : " + psw)
assert 1 == 1

运行结果:

pytest实现用例参数化(@pytest.mark.parametrize)_用例_23

 

去期待陌生,去拥抱惊喜。