pytest实现用例参数化(@pytest.mark.parametrize)
一、pytest实现测试用例参数化(@pytest.mark.parametrize)
@pytest.mark. parametrize装饰器可以实现对测试用例的参数化,方便测试数据的获取。
@pytest.mark. parametrize的基本使用:
方便测试函数对测试数据的获取。 方法: parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)
参数说明:
参数化用法
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'])
运行结果:
从运行结果中可以看到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'])
运行结果:
从运行结果中可以看到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'])
运行结果:
从运行结果中可以看到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'])
运行结果:
从运行结果中可以看到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 != ""
运行结果:
实例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))
运行结果:
详细解释:
整个调用过程如下:
实例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']))
运行结果:
实例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))
运行结果:
实例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.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范围:
②大于fixture函数的scope范围:
③多个SCOPE范围为先执行的:
与③相比调整一下顺序:
四、参数化@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
运行结果:
实例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
运行结果:
实例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
运行结果:
实例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.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
运行结果:
实例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
运行结果:
实例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
运行结果:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!