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

执行结果
Pytest系列-数据驱动@pytest.mark.parametrize(7)_第1张图片

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

Pytest系列-数据驱动@pytest.mark.parametrize(7)_第2张图片
执行结果
Pytest系列-数据驱动@pytest.mark.parametrize(7)_第3张图片

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

执行结果
Pytest系列-数据驱动@pytest.mark.parametrize(7)_第4张图片

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}")

执行结果
Pytest系列-数据驱动@pytest.mark.parametrize(7)_第5张图片
参数化,传入字典

# 字典
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']}")

执行结果
Pytest系列-数据驱动@pytest.mark.parametrize(7)_第6张图片
参数化,标记数据

# 标记参数化
@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

执行结果
Pytest系列-数据驱动@pytest.mark.parametrize(7)_第7张图片

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

执行结果
Pytest系列-数据驱动@pytest.mark.parametrize(7)_第8张图片

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}")

执行结果
Pytest系列-数据驱动@pytest.mark.parametrize(7)_第9张图片
知识点:

  • 添加 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']}")

执行结果
Pytest系列-数据驱动@pytest.mark.parametrize(7)_第10张图片
如果需要传多个参数,需要通过字典去传

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)

执行结果
Pytest系列-数据驱动@pytest.mark.parametrize(7)_第11张图片

@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)

执行结果
Pytest系列-数据驱动@pytest.mark.parametrize(7)_第12张图片

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

你可能感兴趣的:(pytest,pytest)