呕心沥血,看遍百文,问遍【big牛】写出的。pytest实现参数化(params,parametrize等)

目录

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

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

参数说明:

参数化用法

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

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

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

4、参数组合

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

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

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

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

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

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

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

实例:

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

pytest.param用法:

参数:

源码:

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

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

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

实例4:

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

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

实例1:

实例2:

实例3:

【pytest实现参数化(params)】

前言

fixture 源码

fixture 之 params 使用示例

前置与后置


一、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

运行结果:

【pytest实现参数化(params)】

前言

参数化是自动化测试里面必须掌握的一个知识点,用过 unittest 框架的小伙伴都知道使用 ddt 来实现测试用例的参数化。
pytest 测试用例里面对应的参数可以用 parametrize 实现,随着用例的增多,我们的需求也会越来越多,那么如何在 fixture 中使用参数呢?

fixture 源码

先看下 fixture 源码,有这几个参数:scope,params,autouse,ids,name。

def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
    """Decorator to mark a fixture factory function.

    This decorator can be used, with or without parameters, to define a
    fixture function.

    The name of the fixture function can later be referenced to cause its
    invocation ahead of running tests: test
    modules or classes can use the ``pytest.mark.usefixtures(fixturename)``
    marker.

    Test functions can directly use fixture names as input
    arguments in which case the fixture instance returned from the fixture
    function will be injected.

    Fixtures can provide their values to test functions using ``return`` or ``yield``
    statements. When using ``yield`` the code block after the ``yield`` statement is executed
    as teardown code regardless of the test outcome, and must yield exactly once.

    :arg scope: the scope for which this fixture is shared, one of
                ``"function"`` (default), ``"class"``, ``"module"``,
                ``"package"`` or ``"session"``.

                ``"package"`` is considered **experimental** at this time.

    :arg params: an optional list of parameters which will cause multiple
                invocations of the fixture function and all of the tests
                using it.
                The current parameter is available in ``request.param``.

    :arg autouse: if True, the fixture func is activated for all tests that
                can see it.  If False (the default) then an explicit
                reference is needed to activate the fixture.

    :arg ids: list of string ids each corresponding to the params
                so that they are part of the test id. If no ids are provided
                they will be generated automatically from the params.

    :arg name: the name of the fixture. This defaults to the name of the
                decorated function. If a fixture is used in the same module in
                which it is defined, the function name of the fixture will be
                shadowed by the function arg that requests the fixture; one way
                to resolve this is to name the decorated function
                ``fixture_<fixturename>`` and then use
                ``@pytest.fixture(name='<fixturename>')``.
    """
    if callable(scope) and params is None and autouse is False:
        # direct decoration
        return FixtureFunctionMarker("function", params, autouse, name=name)(scope)
    if params is not None and not isinstance(params, (list, tuple)):
        params = list(params)
    return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)

重点看 params 参数:一个可选的参数列表,它将导致多次调用fixture函数和使用它的所有测试
获取当前参数可以使用 request.param

    :arg params: an optional list of parameters which will cause multiple
                invocations of the fixture function and all of the tests
                using it.
                The current parameter is available in ``request.param``.

fixture 之 params 使用示例

request 是pytest的内置 fixture ,主要用于传递参数

运行结果

# test_fixture_params.py

import pytest

# 作者-北凡 QQ交流群:913569736

# blog地址 https://www.cnblogs.com/yoyoketang/

def delete_sql(user):

    '''这里执行SQL'''

    sql = "delete from auth_user WHERE username = '%s';"%user

    print("执行的sql:%s"%sql)

    # 调用执行SQL的封装函数

# 测试数据,存放在list

user_data = ["user1", "user2"]

@pytest.fixture(scope="function", params=user_data)

def users(request):

    '''注册用户参数化'''

    # 前置操作

    delete_sql(request.param)

    yield request.param

    # # 后置操作

    # delete_sql(request.param)

def test_register(users):

    print("注册用户:%s"%users)

if __name__ == '__main__':

    pytest.main(["-s", "test_fixture_params.py"])

>pytest test_fixture_params.py -s
============================= test session starts =============================
platform win32 -- Python 3.6.0, pytest-4.5.0, py-1.5.4, pluggy-0.13.1
rootdir: D:\soft\demo
plugins: allure-pytest-2.8.6
collected 2 items

test_fixture_params.py 注册用户:user1
.注册用户:user2
.

========================== 2 passed in 0.02 seconds ===========================

前置与后置

如果每次注册用户之前,需先在前置操作里面清理用户注册表的数据,可以执行SQL,传不同用户的参数

# test_fixture_params.py

import pytest

# 作者-北凡 QQ交流群:913569736

# blog地址 https://www.cnblogs.com/yoyoketang/

def delete_sql(user):

    '''这里执行SQL'''

    sql = "delete from auth_user WHERE username = '%s';"%user

    print("执行的sql:%s"%sql)

    # 调用执行SQL的封装函数

# 测试数据,存放在list

user_data = ["user1", "user2"]

@pytest.fixture(scope="function", params=user_data)

def users(request):

    '''注册用户参数化'''

    # 前置操作

    delete_sql(request.param)

    yield request.param

    # # 后置操作

    # delete_sql(request.param)

def test_register(users):

    print("注册用户:%s"%users)

if __name__ == '__main__':

    pytest.main(["-s", "test_fixture_params.py"])

运行结果

collected 2 items

test_fixture_params.py 执行的sql:delete from auth_user WHERE username = 'user1';
注册用户:user1
.执行的sql:delete from auth_user WHERE username = 'user2';
注册用户:user2
.

========================== 2 passed in 0.06 seconds ===========================

后置操作可以写到 yield 后面,参考上面的格式。

  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值