pytest教程-8-用例参数化方法

领取资料,咨询答疑,请➕wei:  June__Go

上一小节中我们学习了pytest用例前后置方法的使用,本小节我们讲解一下pytest用例的参数化方法。

参数化简介:

参数化测试是指在测试用例中通过传入不同的参数来运行多次测试,以验证被测函数或方法的不同输入输出。其实也就是数据驱动测试的概念。在 unittest 中,使用ddt库配合unittest实现数据驱动。在pytest中并不需要额外的库,通过pytest.mark.parametrize()即可实现参数化。总之,pytest参数化使得我们可以方便地对测试用例进行扩展,减少了冗余代码,提高了测试的效率。

pytest有以下几种传参方式:

  • @pytest.mark.parametrize() 通过装饰器方式进行参数化(最常使用)
  • pytest.fixture()方式进行参数化,fixture装饰的函数可以作为参数传入其他函数
  • conftest.py文件中存放参数化函数,可作用于模块内的所有测试用例
  • 使用pytest的钩子函数pytest_generate_tests来自定义参数化测试的生成方式
  • 使用pytest-data库:pytest-data是一个用于参数化测试的扩展库,可以通过加载外部数据文件来提供参数化测试的数据
  • 使用自定义参数化装饰器:除了pytest提供的装饰器,还可以自定义参数化装饰器来实现特定的参数化方式

使用parametrize进行参数化

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

@pytest.mark.parametrize('参数化名称',参数化值) 该方法可以作用测试类和测试函数中

1、单个参数(data数据既可以是列表也可以是元组)

data为单一列表

import pytest
data=["lucy","lisa"]

@pytest.mark.parametrize('name',data)
def test_params(name):
    print(f'测试数据是{name}')

data为单一元组

import pytest
data=("lucy","lisa")

@pytest.mark.parametrize('name',data)
def test_params(name):
    print(f'测试数据是{name}')

data为列表里嵌套列表

import pytest
 
data = [
    [1,1,2],
    [2,2,4],
    [3,3,6],
    [4,4,8]
]
@pytest.mark.parametrize("test_data",data)
def test_login(test_data):
    print(f"username is {test_data}")

data为列表里嵌套元组

import pytest
 
data = [
    (1,1,2),
    (2,2,4),
    (3,3,6),
    (4,4,8)
]
@pytest.mark.parametrize("test_data",data)
def test_login(test_data):
    print(f"username is {test_data}")

data为列表里嵌套字典

import pytest
 
data = [{"user": "admin", "password": "123456"},
         {"user": "super", "password": "654321"},
         {"user": "sysadmin", "password": "321456"}
         ]
@pytest.mark.parametrize("test_data",data)
def test_login(test_data):
    print(f"username is {test_data['user']}\n password is {test_data['password']}")

2、多个参数

data为列表里嵌套列表

import pytest
 
data = [
    [1,1,2],
    [2,2,4],
    [3,3,6],
    [4,4,8]
]
@pytest.mark.parametrize("a,b,c",data)
def test_login(a,b,c):
    print(f"\na,b,c is: {a},{b},{c}")

data为列表里嵌套元组

import pytest

data = [
    (1, 1, 2),
    (2, 2, 4),
    (3, 3, 6),
    (4, 4, 8)
]


@pytest.mark.parametrize("a,b,c", data)
def test_login(a, b, c):
    print(f"\na,b,c is: {a},{b},{c}")

3、多个parametrize参数叠加(结果种类为多个参数相乘)

在实际测试中,有的场景多条件查询,比如登录有2个条件,名字有两种密码有四种情况,如果要全部覆盖,则是2*4==8种情况。这种情景,人工测试一般是不会全部覆盖的,但在自动化测试中,只要你想,就可以做到。如下示例:

import pytest

class TestAdd():
    @pytest.mark.parametrize('pwd', [33,None,44, 55])
    @pytest.mark.parametrize('name', [11, 22,])
    def test_add1(self, name, pwd):
        print(f'name:{name} pwd:{pwd}')

运行结果:2*4==8种情况

collected 8 items                                                                                                                                                                                                                                                                                                                                                   

test_demo.py::TestAdd::test_add1[11-33] PASSED                                                                                                                                                                                                                                                                                                                 [1/8]
test_demo.py::TestAdd::test_add1[11-None] PASSED                                                                                                                                                                                                                                                                                                               [2/8]
test_demo.py::TestAdd::test_add1[11-44] PASSED                                                                                                                                                                                                                                                                                                                 [3/8]
test_demo.py::TestAdd::test_add1[11-55] PASSED                                                                                                                                                                                                                                                                                                                 [4/8]
test_demo.py::TestAdd::test_add1[22-33] PASSED                                                                                                                                                                                                                                                                                                                 [5/8]
test_demo.py::TestAdd::test_add1[22-None] PASSED                                                                                                                                                                                                                                                                                                               [6/8]
test_demo.py::TestAdd::test_add1[22-44] PASSED                                                                                                                                                                                                                                                                                                                 [7/8]
test_demo.py::TestAdd::test_add1[22-55] PASSED      

4.1、ids 自定义测试id

通过上面的运行结果,我们可以看到,为了区分参数化的运行结果,在结果中都会显示数据组合而成的名称。

测试结果会自动生成测试id,自动生成的id短小还好说,如果数据比较长而复杂的话,那么就会很难看。

@pytest.mark.parametrize() 提供了 ids 参数来自定义显示结果,就是为了好看易读。

import pytest

class TestAdd():
    @pytest.mark.parametrize('name,pwd', [(10,11),(20,21),(30,31)],ids=(['zhangsan','lisi','wangmazi']))
    def test_add1(self, name, pwd):
        print(f'name:{name} pwd:{pwd}')

运行结果:以zhangsan、zhangsan、wangmazi显示

collected 3 items                                                                                                                                                                                                                                                                                                                                                   

test_demo.py::TestAdd::test_add1[zhangsan] PASSED                                                                                                                                                                                                                                                                                                              [1/3]
test_demo.py::TestAdd::test_add1[zhangsan] PASSED                                                                                                                                                                                                                                                                                                                  [2/3]
test_demo.py::TestAdd::test_add1[wangmazi] PASSED 

4.2 pytest.param 自定义测试id

在参数化测试中,每个测试用例可能包含多组参数,并且可能会产生大量的测试结果。这时,为了更好地理解和调试测试结果,给每个参数化测试用例指定一个易于理解的标识是很有意义的。而 pytest.param 函数的 id 参数就能做到这一点。

import pytest


@pytest.mark.parametrize("input,expected", [pytest.param(2, 4, id="case1"),
                                            pytest.param(3, 9, id="case2"),
                                            pytest.param(5, 25, id="case3")])
def test_multiply(input, expected):
    assert input * input == expected

运行结果:以case1、case2、case3显示

============================= test session starts =============================
collecting ... collected 3 items

test_demo.py::test_multiply[case1] PASSED                                [ 33%]
test_demo.py::test_multiply[case2] PASSED                                [ 66%]
test_demo.py::test_multiply[case3] PASSED                                [100%]

============================== 3 passed in 0.03s ==============================

4.3 pytest.param自定义选项

除了测试id参数,pytest.param 函数还可以接受额外的参数,例如 marks 参数,用于为单个测试用例应用自定义标记。通过使用 marks 参数,我们可以在参数化测试中灵活地添加各种自定义选项。

import pytest


@pytest.mark.parametrize("input,expected", [pytest.param(2, 4, id="case1"),
                                            pytest.param(3, 10, id="case2", marks=pytest.mark.xfail),
                                            pytest.param(5, 25, id="case3", marks=pytest.mark.skip)])
def test_multiply(input, expected):
    assert input * input == expected

运行结果:


test_demo.py::test_multiply[case1] 
test_demo.py::test_multiply[case2] 
test_demo.py::test_multiply[case3] 

=================== 1 passed, 1 skipped, 1 xfailed in 0.25s ===================
PASSED                                [ 33%]XFAIL                                 [ 66%]
input = 3, expected = 10

    @pytest.mark.parametrize("input,expected", [pytest.param(2, 4, id="case1"),
                                                pytest.param(3, 10, id="case2", marks=pytest.mark.xfail),
                                                pytest.param(5, 25, id="case3",marks=pytest.mark.skip)])
    
    def test_multiply(input, expected):
>       assert input * input == expected
E       assert (3 * 3) == 10

在上述示例中,我们使用 pytest.mark.skip, pytest.mark.xfail标记这两个测试用例。通过这种方式,我们可以对不同的参数化测试用例应用不同的标记,以实现更加灵活的测试控制 。

5、使用CSV或Excel文件作为测试数据进行参数化

可以使用Python的csv或openpyxl库来读取文件并生成参数组合。以下是一个示例:

import pytest
import csv

def read_csv(file_path):
    with open(file_path, 'r') as csv_file:
        csv_reader = csv.DictReader(csv_file)
        for row in csv_reader:
            yield row

@pytest.mark.parametrize("data", read_csv("test_data.csv"))
def test_multiply(data):
    num1 = int(data['num1'])
    num2 = int(data['num2'])
    expected = int(data['expected'])
    assert num1 * num2 == expected

使用pytest.fixture进行参数化

上面介绍了pytest中的自带的参数化方法,我们也可以通过使用fixture中的params参数来做参数化。

request.param:用于获取测试的请求参数。【获取测试上下文的信息】

①【注意】fixture函数的 params 请求参数数量(请求参数的数据类型为列表/元组,请求参数数量为列表/元组元素个数)决定fixture函数执行的次数。

②【注意】此时fixture函数的装饰器 @pytest.fixture(params=get_data) 参数不能忘记传值。

1、单个参数

import pytest
data3 = [{"user": "admin", "password": "123456"},
         {"user": "super", "password": "654321"},
         {"user": "sysadmin", "password": "321456"}
         ]
@pytest.fixture(params=data3,autouse=True,scope="class")
def get_data(request):
    print("fixture begin")
    yield request.param
    print("fixture end")
 
class TestLogin:
    def test_login(self,get_data):
        test_data = get_data
        print("username is {} AND password is {}".format(test_data["user"], test_data["password"]))

2、多个参数

import pytest
#此处是列表嵌套元祖
data1 = [('admin', '12346'), ("super", "654321"), ("sysadmin", '321456')]
data2 = [('admin', '12346'), ("super", "654321"), ("sysadmin", '321456')]

@pytest.fixture(params=data1,autouse=True,scope="class")
def get_data_1(request):
    print("fixture begin")
    yield request.param
    print("fixture end")

@pytest.fixture(params=data2,autouse=True,scope="class")
def get_data_2(request):
    print("fixture begin")
    yield request.param
    print("fixture end")
class TestLogin:
    def test_login(self,get_data_1,get_data_2):
        test_data_1 = get_data_1
        test_data_2 = get_data_2
        print("username is {};password is {}".format(get_data_1, get_data_2))

3、pytest.fixture与parametrize结合一起使用

fixture自身的params参数可以结合request来传参,当然也可以用parametrize来参数化代替params

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

单个参数

import pytest

seq = [1, 2, 3]

@pytest.fixture()
def ss_data(request):
    print("\n参数 %s" % request.param)
    return request.param + 1

class TestData:
    @pytest.mark.parametrize("ss_data", seq, indirect=True)
    def test_1(self, ss_data):
        print("用例", ss_data)

多个fixture和多个parametrize叠加

import pytest

seq1 = [1, 2, 3]
seq2 = [4, 5, 6]

@pytest.fixture()
def get_seq1(request):
    seq1 = request.param
    print("seq1:", seq1)
    return seq1

@pytest.fixture()
def get_seq2(request):
    seq2 = request.param
    print("seq2:", seq2)
    return seq2

@pytest.mark.parametrize("get_seq1", seq1, indirect=True)
@pytest.mark.parametrize("get_seq2", seq2, indirect=True)
def test_1(get_seq1, get_seq2):
    print(get_seq1, 11)
    print(get_seq2, 22)

使用conftest.py进行参数化

conftest.py特点:

  • conftest.py 文件中存放参数化函数,可作用于模块内的所有测试用例
  • conftest.py 配置里可以实现数据共享,不需要import就能自动找到一些配置,pytest默认读取里面的配置

conftest.py配置需要注意以下点:

  • conftest.py配置脚本名称是固定的,不能改名称
  • conftest.py与运行的用例要在同一个pakage下,并且有__init__.py文件
  • 不需要import导入 conftest.py,pytest用例会自动查找

conftest.py应用场景

1、每个接口需共用到的token

conftest.py

import pytest
 
@pytest.fixture(scope='session')
def get_token():
    token = 'qeehfjejwjwjej11sss112'
    return token

test_params.py

def test_get_token(get_token):
    token = get_token
    print(token)

2、每个接口需共用到的测试用例数据

编写一个fixture在conftest.py,内容如下

data = [["admin","123456"],["superadmin","654321"]]
@pytest.fixture(scope='session',params=data)
def get_test_data(request):
    yield request.param

在测试脚本里面使用,只需要引入即可,新建一个测试文件test_params.py,内容如下

def test_get_data(get_test_data):
    print(f"user is {get_test_data[0]} and pwd is {get_test_data[1]}")

3、每个接口需共用到的配置信息

编写一个fixture在conftest.py,内容如下

@pytest.fixture(scope='session')
def get_base_url():
    base_url = "http://www.baidu.com"
    return base_url

在测试脚本里面使用,只需要引入即可,新建一个测试文件test_params.py,内容如下

def test_base_url(get_base_url):
    url = get_base_url
    print(url)

使用pytest_generate_tests钩子函数参数化

可以使用pytest的钩子函数pytest_generate_tests来自定义参数化测试的生成方式。下面是一个示例:

import pytest

def pytest_generate_tests(metafunc):
    if 'num' in metafunc.fixturenames:
        metafunc.parametrize('num', [1, 2, 3])

def test_square(num):
    assert num ** 2 == num * num

在上面的示例中,定义了一个pytest_generate_tests钩子函数,通过判断测试函数的参数是否存在来进行参数化。每个参数组合都会作为单独的测试用例执行。

使用pytest-data库参数化

pytest-data是一个用于参数化测试的扩展库,可以通过加载外部数据文件来提供参数化测试的数据。以下是一个示例:

import pytest
from pytest_data import data

@pytest.mark.datafiles('test_data.csv')
def test_addition(datafiles):
    data_file = datafiles / 'test_data.csv'
    for row in data(data_file):
        num1 = row['num1']
        num2 = row['num2']
        expected = row['expected']
        assert num1 + num2 == expected

在上面的示例中,使用pytest-data库的data装饰器加载了一个CSV文件作为测试数据,并在测试函数中使用了这些数据进行参数化测试。

使用自定义参数化装饰器进行参数化

除了pytest提供的装饰器,还可以自定义参数化装饰器来实现特定的参数化方式。示例如下

import pytest

def custom_parametrize(*args):
    def decorator(func):
        for arg in args:
            func = pytest.mark.parametrize(*arg)(func)
        return func
    return decorator

@custom_parametrize(
    ("num", [1, 2, 3]),
    ("operation", ["add", "subtract"])
)
def test_calculator(num, operation):
    if operation == "add":
        result = num + num
        assert result == 2 * num
    elif operation == "subtract":
        result = num - num
        assert result == 0

在上面的示例中,定义了一个自定义的参数化装饰器custom_parametrize,接受一系列参数化参数,并将其应用于测试函数。使用自定义装饰器可以实现更复杂的参数化逻辑。

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走,希望可以帮助到大家!领取资料,咨询答疑,请➕wei:  June__Go

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