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

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

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

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

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

参数说明:

image

参数化用法

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'])

运行结果:

image

从运行结果中可以看到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'])

运行结果:

image

从运行结果中可以看到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'])

运行结果:

image

从运行结果中可以看到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'])

运行结果:

image

从运行结果中可以看到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 != ""

运行结果:

image

实例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))

运行结果:

image

详细解释:

image

整个调用过程如下:

image

实例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']))

运行结果:

image

实例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))

运行结果:

image

实例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))

运行结果:

image

三、参数化@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范围:

image

②大于fixture函数的scope范围:

image

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

image

与③相比调整一下顺序:

image

四、参数化@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

运行结果:

image

实例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

运行结果:

image

实例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

运行结果:

image

实例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

运行结果:

image

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

①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

运行结果:

image

实例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

运行结果:

image

实例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

运行结果:

 自动化测试相关教程推荐:

2023最新自动化测试自学教程新手小白26天入门最详细教程,目前已有300多人通过学习这套教程入职大厂!!_哔哩哔哩_bilibili

2023最新合集Python自动化测试开发框架【全栈/实战/教程】合集精华,学完年薪40W+_哔哩哔哩_bilibili

测试开发相关教程推荐

2023全网最牛,字节测试开发大佬现场教学,从零开始教你成为年薪百万的测试开发工程师_哔哩哔哩_bilibili

postman/jmeter/fiddler测试工具类教程推荐

讲的最详细JMeter接口测试/接口自动化测试项目实战合集教程,学jmeter接口测试一套教程就够了!!_哔哩哔哩_bilibili

2023自学fiddler抓包,请一定要看完【如何1天学会fiddler抓包】的全网最详细视频教程!!_哔哩哔哩_bilibili

2023全网封神,B站讲的最详细的Postman接口测试实战教学,小白都能学会_哔哩哔哩_bilibili

  总结:

 光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

如果对你有帮助的话,点个赞收个藏,给作者一个鼓励。也方便你下次能够快速查找。

如有不懂还要咨询下方小卡片,博主也希望和志同道合的测试人员一起学习进步

在适当的年龄,选择适当的岗位,尽量去发挥好自己的优势。

我的自动化测试开发之路,一路走来都离不每个阶段的计划,因为自己喜欢规划和总结,

测试开发视频教程、学习笔记领取传送门!!

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐