文章目录
- 前言
- Parametrize 参数化
- Parametrize 源码分析
- Parametrize 使用说明
- 一个参数的参数化
- 多个参数的参数化
- 验证类中有多个测试函数
- 验证变量或函数传递参数化
- 验证笛卡尔积
- 拓展用法
前言
在
pytest
中,有两种常见的参数化方法:@pytest.mark.parametrize装饰器:
@pytest.mark.parametrize
是pytest
中最直接的参数化方法。它允许我们为测试函数提供多组输入和期望的输出,这样测试函数就可以使用不同的参数组合多次执行。
Fixture:
Fixture
是pytest
中的一个强大功能,允许我们创建一些可以在多个测试之间共享的设置和清理代码。虽然fixture
本身不是用来参数化的,但我们可以使用它们来实现参数化的效果应用场景:
- @pytest.mark.parametrize 适用于简单的参数化场景,其中每组参数都是静态的,不需要进行复杂的设置或清理。
- Fixture实现的参数化 是通过创建多个
fixtures
来实现的,每个fixture
可以包含不同的测试数据或状态。它适用于需要更复杂设置和清理的场景,或者当我们想在不同的测试之间共享相同的设置代码时。
Parametrize 参数化
前文
Pytest速查表(08)利用Fixture实现参数化
讲解了利用Fixture
实现参数化,同学们可以查漏补缺;本文讲解一下
Parametrize
参数化,Parametrize
也是一个内置标记,在命令行中通过pytest --markers
可以看到。
Parametrize 源码分析
1.在
pytest
的源码中,_ParametrizeMarkDecorator
类是一个内部类,用于实现@pytest.mark.parametrize
装饰器的功能。
2.我们看到的
...
是Python
类型注解中的Ellipsis
对象,通常用作占位符,表示该参数有默认值,但在这里它可能是用于表示参数的具体实现细节尚未在此处展开。
3.当使用
@pytest.mark.parametrize
装饰器来装饰一个测试函数时,pytest
的内部机制会捕获这个装饰器并处理它;
4.
pytest
会创建一个_ParametrizeMarkDecorator
的实例,并将装饰器参数(如argnames
,argvalues
等)传递给这个实例;
5.然后,当运行测试时,
pytest
会识别这个装饰器并调用其__call__
方法,以生成多个测试实例,每个实例都有不同的参数集。
6.虽然在这段代码中看不到
__call__
方法的实际实现(由...
表示),但可以假设pytest
在内部实现了这个方法,以便处理参数化逻辑。具体来说,__call__
方法可能会:
- 验证
argnames
和argvalues
参数的有效性和兼容性。- 根据
argnames
和argvalues
生成多个测试实例。- 处理
indirect
参数,以确定哪些参数应该通过pytest
的fixture机制间接提供。- 处理
ids
参数,为每个测试实例生成一个唯一的标识符。- 处理
scope
参数,以确定参数化的作用域。
class _ParametrizeMarkDecorator(MarkDecorator):def __call__( # type: ignore[override]self,argnames: Union[str, Sequence[str]],argvalues: Iterable[Union[ParameterSet, Sequence[object], object]],*,indirect: Union[bool, Sequence[str]] = ...,ids: Optional[Union[Iterable[Union[None, str, float, int, bool]],Callable[[Any], Optional[object]],]] = ...,scope: Optional[_ScopeName] = ...,) -> MarkDecorator:...
最终根据
__call__
方法得出结论,Parametrize方法完整体是:
@pytest.mark.parametrize(argnames, argvalues, indirect=False, ids=None, scope=None))
Parametrize实例参数的含义和用途如下:
argnames:
- 含义:
argnames
参数定义了将要传递给测试函数的参数名称。它可以是一个字符串(表示单个参数名称)或一个字符串序列(表示多个参数名称)。
- 用途: 这个参数告诉
pytest
在执行测试函数时需要传递哪些参数,以及这些参数的顺序。argvalues:
- 含义:
argvalues
是一个可迭代对象,它包含了要传递给测试函数的参数值。这些值应该按照
argnames
中定义的顺序排列。
- 用途: 这个参数允许我们为
argnames
中定义的每个参数提供一组或多组值,以便多次运行测试函数。indirect:
- 含义:
indirect
参数是一个布尔值或一个字符串序列。如果设置为
True
,则argvalues
中的每个值都应被解释为 fixture 名称的字符串,而不是直接的参数值。如果是一个字符串序列,则它应该与
argnames
中的参数名称相对应,指示哪些参数应该通过fixture
来间接提供。
- 用途: 这个参数允许我们指定某些参数值不是直接传递给测试函数的,而是通过
pytest
的fixture
机制间接提供的。这对于需要复杂设置或共享状态的测试非常有用。ids:
- 含义:
ids
参数是一个可选参数,它可以是一个可迭代对象或一个可调用对象。如果是一个可迭代对象,它应该与
argvalues
中的每组参数值相对应,为每个测试实例提供一个唯一的标识符。如果是一个可调用对象,它应该接收一个参数值并返回一个标识符。
- 用途:
ids
参数允许我们为每组参数值提供一个自定义的标识符,这在生成的测试报告中可以使测试结果更容易区分。scope:
- 含义:
scope
参数是一个可选参数,它指定了参数化的作用域。作用域可以是
"session"
,"class"
,"function"
, 或"module"
。
- 用途: 这个参数定义了参数化的生命周期。
例如,如果
scope
设置为"session"
,则参数化将在整个测试会话中保持有效,而不是为每个测试函数重新创建。这可以影响参数化值的创建和销毁时间,以及它们在不同测试之间的共享程度。
Parametrize 使用说明
@pytest.mark.parametrize
装饰器可以用来装饰测试函数、测试方法(类中的方法)和测试类。它的主要目的是为被装饰的测试方法提供多组输入数据和预期的输出数据,以执行多次测试。当
@pytest.mark.parametrize
装饰器应用于一个测试类时,它会为该类下的每个测试方法提供指定的测试数据。这意味着测试类中的所有测试方法都会使用相同的数据集进行多次测试。在一个测试方法上应用了一个
@pytest.mark.parametrize
装饰器,并提供了N组数据,那么这个方法将会被调用N次,每次使用一组不同的数据可以在一个函数或方法上应用多个
@pytest.mark.parametrize
装饰器,但这样做时,它们会生成笛卡尔积,即每个装饰器分别提供了X、Y、Z组数据,那么这个方法将会被调用X*Y*Z
次。(每个argnames
和argvalues
的组合都会被测试一次)。对于测试类,通常不建议在类级别应用多个参数化装饰器,而是在类的方法上分别应用。
一个参数的参数化
示例代码
**注意:**测试方法形参名要和
parametrize
里面的参数一样
import pytest@pytest.mark.parametrize("data1", [1, 3, 5, 7, 9])
class TestClassDemo1:def test_case_01(self, data1):print(f"data1 = {data1}")
执行结果
多个参数的参数化
示例代码
import pytest@pytest.mark.parametrize(argnames="entered,expected",argvalues=[["1+1", 2], ["2*3", 6], ["1-1", 0]])
def test_case_01(entered, expected):print(f"entered={entered}, eval(entered)={eval(entered)}, expected={expected}")assert eval(entered) == expected
执行结果
验证类中有多个测试函数
示例代码
import pytest@pytest.mark.parametrize("data1", [1, 3, 5, 7, 9])
class TestClassDemo1:def test_case_01(self, data1):print(f"data1-1 = {data1}")def test_case_02(self, data1):print(f"data1-2 = {data1}")def test_case_03(self, data1):print(f"data1-3 = {data1}")
执行结果
验证变量或函数传递参数化
示例代码
import pytestdata = [["1+1", 2], ["2*3", 6], ["1-1", 0]]def get_data():return data# @pytest.mark.parametrize(argnames="entered,expected", argvalues=data)
@pytest.mark.parametrize(argnames="entered,expected", argvalues=get_data())
def test_case_01(entered, expected):print(f"entered={entered}, eval(entered)={eval(entered)}, expected={expected}")assert eval(entered) == expected
执行结果
验证笛卡尔积
示例代码
import pytest@pytest.mark.parametrize("username", ["admin", "root", "test"])
@pytest.mark.parametrize("password", ["admin", "root", "test"])
class TestClassDemo4:def test_case_04(self, username, password):print(f"name={username}, technology={password}")# parametrize与fixture混合也会出现笛卡尔积效果
# @pytest.fixture(params=["admin", "root", "test"])
# def username(request):
# return request.param# @pytest.mark.parametrize("password", ["admin", "root", "test"])
# def test_case(username, password):
# print(f"name={username}, password={password}")
执行效果
拓展用法
添加
pytest.ini
配置信息
[pytest]
addopts = -s --strict-markers
markers =mark1mark2mark3
示例代码
import pytest@pytest.mark.parametrize("entered,expected",[("1+1", 2),pytest.param("2*3", 6, marks=[pytest.mark.mark1, pytest.mark.skip], id="test 2*3"),pytest.param("1-1", 0, marks=[pytest.mark.mark2, pytest.mark.xfail], id="test 1-1"),pytest.param("3+7", 10, marks=[pytest.mark.mark1,pytest.mark.skipif(condition=True, reason="Skipping multiplication test")],id="test 3+7"),pytest.param("4/2", 2, marks=pytest.mark.mark3, id="test 4/2")]
)
def test_case_01(entered, expected):print(f"entered={entered}, eval(entered)={eval(entered)}, expected={expected}")assert eval(entered) == expected
执行结果