• Pytest系列-数据驱动@pytest.mark.parametrize(7)


    简介

    unittest 和 pytest参数化对比:
    pytest与unittest的一个重要区别就是参数化,unittest框架使用的第三方库ddt来参数化的

    而pytest框架:

    • 前置/后置处理函数fixture,它有个参数params专门与request结合使用来传递参数,也可以用parametrize结合request来传参
    • 针对测试方法参数化就直接使用装饰器@pytest.mark.parametrize来对测试用例进行传参

    参数化目的

    • 参数化,就是把测试过程中的数据提取出来,通过参数传递不同的数据来驱动用例运行。其实也就是数据驱动的概念
    • 当测试用例只有测试数据和期望结果不一样,但操作步骤是一样的时,可以用参数化提高代码复用性,减少代码冗余

    参数化实际应用举例

    Web UI自动化中的开发场景,比如是一个登录框:

    1、需要测试账号空、密码空、账号密码都为空、账号不存在、密码错误、账号密码正确等情况
    2、这些用例的区别就在于输入的测试数据和对应的交互结果
    3、所以我们可以只写一条登录测试用例,然后把多组测试数据和期望结果参数化,节省很多代码量

    Pytest参数化

    语法

    @pytest.mark.parametrize(args_name,args_values,indirect=False, ids=None,scope=None)

    参数列表

    • args_name:参数名称,用于参数值传递给函数
    • args_values:参数值:(列表和列表字典,元组和字典元组),有n个值那么用例执行n次。
    • indirect:默认为False,代表传入的是参数。如果设置成True,则把传进来的参数当函数执行,而不是一个参数。
    • ids:自定义测试id,字符串列表,ids的长度需要与测试数据列表的长度一致,标识每一个测试用例,自定义测试数据结果的显示,为了增加可读性
    • scope:如果指定,则表示参数的范围。
      范围用于按参数实例对测试进行分组。
      它还将覆盖任何夹具功能定义的范围,允许使用测试上下文或配置设置动态范围。

    使用方法

    1、单个参数

    ​​@pytest.mark.parametrize()​​ 装饰器接收两个参数,一个参数是以字符串的形式标识用例函数的参数,第二个参数以列表或元组的形式传递测试数据

    import pytest
    #待测试数据
    def add(a,b):
        return a+b
    
    # 单个参数的情况
    @pytest.mark.parametrize("a",[1,2,3,4])
    def test_add(a):  # 作为用例参数,接收装饰器传入的数据
        print(" a的值:",a)
        assert add(a,1) == a+1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    执行结果
    在这里插入图片描述

    2、多个参数

    多个参数,​​@pytest.mark.parametrize()​​第一个参数依然是字符串, 对应用例的多个参数,用逗号分隔,实际是一个解包的过程。

    import pytest
    
    def add(a,b):
        return a+b
    
    @pytest.mark.parametrize("a,b,c",[(1,2,3),(4,5,9),('1','2','12')])
    def test_add(a,b,c):
        print(f"\n a,b,c的值:{a},{b},{c}")
        assert add(a,b)==c
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述
    执行结果
    在这里插入图片描述

    3、整个测试类参数化

    测试类的参数化,其实际上也是对类中的测试方法进行参数化。
    当装饰器 @pytest.mark.parametrize 装饰测试类时,会将数据集合传递给类的所有测试用例方法

    import pytest
    
    data= [(1,2,3),(4,5,9)]
    
    @pytest.mark.parametrize("a,b,c",data)
    class TestStudy:
        def test_parametrize_one(self,a,b,c):
            print(f"\n 测试函数111  测试数据为 {a}-{b}")
            assert a+b ==c
    
        def test_parametrize_two(self, a, b, c):
            print("\n 测试函数222  测试数据为 {}-{}".format(a,b))
            assert a + b == c
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    执行结果
    在这里插入图片描述

    4、多个参数叠加相乘

    多个参数组合时使用的是笛卡尔积
    笛卡尔积:

    • 一个函数或一个类可以装饰多个 @pytest.mark.parametrize
    • 这种方式,最终生成的用例数是nm,比如上面的代码就是:参数a的数据有3个,参数b的数据有2个,所以最终的用例数有32=6条
    • 当参数化装饰器有很多个的时候,用例数都等于n * n * n* n *…
    # 笛卡尔积,组合数据
    data_1=[1,2,3]
    data_2=['a','b']
    
    @pytest.mark.parametrize('a',data_1)
    @pytest.mark.parametrize('b',data_2)
    def test_parametrize_one(a,b):
        print(f"笛卡尔积 测试数据为:{a}{b}")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    执行结果
    在这里插入图片描述
    参数化,传入字典

    # 字典
    data_1=(
        {
            'user':1,
            'pwd':2
        },
        {
            'user':3,
            'pwd':4
        }
    )
    
    @pytest.mark.parametrize('dic',data_1)
    def test_parametrize_1(dic):
        print(f"测数据为 \n {dic}")
        print(f"user:{dic['user']},pwd:{dic['pwd']}")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    执行结果
    在这里插入图片描述
    参数化,标记数据

    # 标记参数化
    @pytest.mark.parametrize("test_input,expected",[
        ("3+5",8),
        ("2+4",6),
        pytest.param("6 * 9",42,marks=pytest.mark.xfail),
        pytest.param("6 * 6",42,marks=pytest.mark.skip)
    ])
    def test_parametrize_mark(test_input,expected):
        assert  eval(test_input) == expected
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    执行结果
    在这里插入图片描述

    5、ids 自定义测试id

    • ​​@pytest.mark.parametrize()​​​ 提供了 ​​ids​​ 参数来自定义显示结果,主要是为了更加清晰看到用例的含义

    • ids的长度需要与测试数据列表的长度一致,与fixture的ids参数是一样的,可参考fixture的ids。

    # 增加可读性
    data_1=[(1,2,3),(4,5,9)]
    
    ids = ["a:{} + b:{} = expect:{}".format(a,b,expect) for a,b,expect in data_1]
    
    @pytest.mark.parametrize("a,b,expect",data_1,ids=ids)
    class TestParametrize(object):
        def test_parametrize_1(self,a,b,expect):
            print(f"测试函数1测试数据为 {a}--{b}")
            assert a + b ==expect
    
        def test_parametrize_2(self,a,b,expect):
            print("测试函数2测试数据为 {}--{}".format(a,b))
            assert  a + b == expect
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    执行结果
    在这里插入图片描述

    request 与parametrize 结合使用给 Fixture传参

    • fixture自身的params参数可以结合request来传参,详见fixture的其他参数介绍章节,当然也可以用parametrize来参数化代替params

    • indirect=True参数,目的是把传入的data当做函数去执行,而不是参数

    • 如果测试方法写在类中,则 @pytest.mark.parametrize 的参数名称要与 @pytest.fixture 函数名称保持一致

    应用场景:

    • 为了提高复用性,我们在写测试用例的时候,会用到不同的fixture,比如:最常见的登录操作,大部分的用- 例的前置条件都是登录
    • 假设不同的用例想登录不同的测试账号,那么登录fixture就不能把账号写死,需要通过传参的方式来完成登录操作

    1、单个参数

    @pytest.fixture()
    def test_demo(request):
        name = request.param
        yield name
        print(f"==测试数据是:{name}==")
    
    
    data=["ceshi","qianduan"]
    ids=[f"test_name is:{name}" for name in data]
    
    @pytest.mark.parametrize("test_demo",data,ids=ids,indirect=True)
    def test_name(test_demo):
        print(f"测试用例的数据是:{test_demo}")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    执行结果
    在这里插入图片描述
    知识点:

    • 添加 indirect=True 参数是为了把 test_demo 当成一个函数去执行,而不是一个参数并且将data当做参数传入函数
    • def test_name(test_demo) ,这里的 test_demo 是获取fixture返回的值

    2、多个参数

    @pytest.fixture()
    def logins(request):
        param = request.param
        yield param
        print(f"账号是:{param['username']},密码是:{param['pwd']}")
    
    data =[
        {"username":"张三","pwd":"123456"},
        {"username":"李四","pwd":"12345"}
    ]
    
    @pytest.mark.parametrize("logins",data,indirect=True)
    def test_name_pwd(logins):
        print(f"账号是:{logins['username']},密码是:{logins['pwd']}")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    执行结果
    在这里插入图片描述
    如果需要传多个参数,需要通过字典去传

    3、多个fixture(只加一个装饰器)和多个parametrize(叠加装饰器)

    # 多个fixture
    @pytest.fixture()
    def login_user(request):
        user = request.param
        yield user
        print("账号:%s" % user)
    
    @pytest.fixture()
    def login_pwd(request):
        pwd = request.param
        yield pwd
        print("密码:%s" % pwd)
    
    data =[
        {"username":"张三","pwd":"123456"},
        {"username":"李四","pwd":"12345"}
    ]
    
    @pytest.mark.parametrize("login_user,login_pwd",data,indirect=True)
    def test_more_fixture(login_user,login_pwd):
        print("fixture返回的内容:",login_user,login_pwd)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    执行结果
    在这里插入图片描述

    @pytest.fixture(scope="function")
    def login_user(request):
        user = request.param
        yield user
        print("账号:%s" % user)
    
    @pytest.fixture(scope="function")
    def login_pwd(request):
        pwd = request.param
        yield pwd
        print("密码:%s" % pwd)
    
    name= ["张三","李四"]
    pwd = ["123456","12345"]
    
    @pytest.mark.parametrize("login_user",name,indirect=True)
    @pytest.mark.parametrize("login_pwd",pwd,indirect=True)
    def test_more_fixture(login_user,login_pwd):
        print("fixture返回的内容:",login_user,login_pwd)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    执行结果
    在这里插入图片描述

    [参考文章] 小菠萝测试笔记

  • 相关阅读:
    最小栈、栈的弹出(C++)
    RapidEye快鸟、SPOT卫星遥感影像数据
    关于RSA加解密的异常 javax.crypto.BadPaddingException: block incorrect
    Redis搭建主从同步流程及原理
    Cookie 与 Session的区别
    JUC P7 线程安全集合 基础+代码
    Linux | crontab定时任务及开机自启项
    FFmpeg和SDL实现视频播放器之 ⌈音频播放⌋
    【牛客网】排序子序列
    HttpClient笔记
  • 原文地址:https://blog.csdn.net/m0_62091240/article/details/132839957