pytest8.x版本 中文使用文档-------27.示例:参数化测试

目录

基于命令行生成参数组合

测试ID的不同选项

“testscenarios”的快速移植版

推迟参数化资源的设置

间接参数化

对特定参数应用间接参数化

通过每个类配置参数化测试方法

使用多个夹具(fixtures)进行参数化

可选实现/导入的参数化 

为单个参数化测试设置标记或测试ID

参数化条件异常抛出


pytest 允许你轻松地参数化测试函数。对于基础文档,请参阅如何参数化固件和测试函数 How to parametrize fixtures and test functions.。

以下我们将通过内置机制提供一些示例。

基于命令行生成参数组合

假设我们想要使用不同的计算参数来执行一个测试,并且这些参数的范围应该由命令行参数来确定。首先,我们编写一个简单的(不执行实际计算)的计算测试:

# content of test_compute.pydef test_compute(param1):assert param1 < 4

现在,我们添加一个测试配置,如下所示:

# conftest.py 文件的内容  def pytest_addoption(parser):  # 向 pytest 添加一个命令行选项 "--all",如果指定,则执行所有组合  parser.addoption("--all", action="store_true", help="run all combinations")  def pytest_generate_tests(metafunc):  # 这是一个 pytest 钩子函数,用于在测试执行前生成测试参数  if "param1" in metafunc.fixturenames:  # 检查是否指定了 "--all" 命令行选项  if metafunc.config.getoption("all"):  # 如果指定了 "--all",则设置 end 为 5  end = 5  else:  # 否则,设置 end 为 2  end = 2  # 使用 parametrize 方法为测试函数 param1 参数生成参数列表  metafunc.parametrize("param1", range(end))

首先通过 pytest_addoption 钩子函数向 pytest 添加了一个新的命令行选项 --all。这个选项是一个布尔标志,当在命令行中指定时(例如,使用 pytest --all 命令),pytest 会将其值设置为 True。然后,我们定义了 pytest_generate_tests 钩子函数,该函数在 pytest 即将执行测试之前被调用。在这个函数中,我们检查即将执行的测试函数是否依赖于名为 param1 的 fixture(通过检查 metafunc.fixturenames)。如果是这样,我们根据命令行上是否指定了 --all 选项来决定 param1 参数的取值范围。如果指定了 --all,则 param1 将从 0 到 4(即 range(5))取值;如果没有指定 --all,则 param1 将仅从 0 到 1(即 range(2))取值。

这意味着,如果我们不使用--all选项,那么我们只运行2个测试:

$ pytest -q test_compute.py
..                                                                   [100%]
2 passed in 0.12s

我们只运行了两个计算,所以我们看到了两个点(.)。现在,让我们运行完整的测试集:

$ pytest -q --all
....F                                                                [100%]
================================= FAILURES =================================
_____________________________ test_compute[4] ______________________________param1 = 4def test_compute(param1):
>       assert param1 < 4
E       assert 4 < 4test_compute.py:4: AssertionError
========================= short test summary info ==========================
FAILED test_compute.py::test_compute[4] - assert 4 < 4
1 failed, 4 passed in 0.12s

正如预期,当我们运行param1参数值的完整范围时,最后一个测试会失败。这是因为param1的值为4时,断言param1 < 4不成立,从而触发了AssertionError

metafunc是一个特殊的参数,它用于pytest_generate_tests钩子函数中。这个钩子函数允许开发者在测试执行前动态地生成测试用例或测试参数。metafunc对象包含以下主要属性和方法:

  1. fixturenames:一个包含当前测试函数或测试类中所有fixture名称的列表。这些fixture是在测试函数或测试类中通过参数形式声明的,Pytest会尝试为它们找到对应的fixture函数或值。

  2. module:表示当前测试函数或测试类所在的模块对象。这可以用于访问模块级别的变量或函数。

  3. config:Pytest的配置对象,包含了Pytest的配置信息,如命令行选项等。通过metafunc.config可以访问这些配置信息,并根据它们来定制测试行为。

  4. function:当前正在生成的测试用例所对应的函数对象。这可以用于获取函数的名称、签名等信息。

  5. cls(如果适用):当前正在生成的测试用例所属的类对象。这在使用类级别的fixture或测试方法时特别有用。

  • parametrize:这是metafunc对象提供的一个方法,用于为测试函数或测试类的参数动态地生成参数值。它接受一个或多个参数名称和对应的参数值列表(或可迭代对象),并为每个参数值生成一个新的测试用例。

测试ID的不同选项

pytest会为参数化测试中的每组值构建一个字符串作为测试ID。这些ID可以使用-k选项来选择要运行的特定情况,并且在某个情况失败时,它们还将标识出具体的失败情况。运行pytest时加上--collect-only选项将显示生成的ID。

数字、字符串、布尔值和None在测试ID中将使用它们通常的字符串表示。对于其他对象,pytest将基于参数名生成一个字符串:

# content of test_time.pyfrom datetime import datetime, timedeltaimport pytesttestdata = [(datetime(2001, 12, 12), datetime(2001, 12, 11), timedelta(1)),(datetime(2001, 12, 11), datetime(2001, 12, 12), timedelta(-1)),
]@pytest.mark.parametrize("a,b,expected", testdata)
def test_timedistance_v0(a, b, expected):diff = a - bassert diff == expected@pytest.mark.parametrize("a,b,expected", testdata, ids=["forward", "backward"])
def test_timedistance_v1(a, b, expected):diff = a - bassert diff == expecteddef idfn(val):if isinstance(val, (datetime,)):# note this wouldn't show any hours/minutes/secondsreturn val.strftime("%Y%m%d")@pytest.mark.parametrize("a,b,expected", testdata, ids=idfn)
def test_timedistance_v2(a, b, expected):diff = a - bassert diff == expected@pytest.mark.parametrize("a,b,expected",[pytest.param(datetime(2001, 12, 12), datetime(2001, 12, 11), timedelta(1), id="forward"),pytest.param(datetime(2001, 12, 11), datetime(2001, 12, 12), timedelta(-1), id="backward"),],
)
def test_timedistance_v3(a, b, expected):diff = a - bassert diff == expected

test_timedistance_v0中,没有指定 ids 参数,我们让pytest自动生成测试ID。

test_timedistance_v1中,我们将ids指定为一个字符串列表,"forward" 和 "backward"用作测试ID,分别对应 testdata 列表中的每个测试案例这些字符串被用作测试ID。这些ID简洁明了,但可能难以维护。

test_timedistance_v2中,我们将ids指定为一个函数idfn,函数检查传入的值是否为 datetime 类型,并返回该日期(不包括时间)的格式化字符串作为测试ID的一部分。由于 idfn 只处理 datetime 类型的值,timedelta 对象仍然使用 pytest 的默认表示方式作为测试ID的一部分:

$ pytest test_time.py --collect-only
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 8 items<Dir parametrize.rst-200><Module test_time.py><Function test_timedistance_v0[a0-b0-expected0]><Function test_timedistance_v0[a1-b1-expected1]><Function test_timedistance_v1[forward]><Function test_timedistance_v1[backward]><Function test_timedistance_v2[20011212-20011211-expected0]><Function test_timedistance_v2[20011211-20011212-expected1]><Function test_timedistance_v3[forward]><Function test_timedistance_v3[backward]>======================== 8 tests collected in 0.12s ========================

test_timedistance_v3中,我们使用pytest.param来同时指定测试ID和实际数据,而不是将它们分别列出。

“testscenarios”的快速移植版

这里是一个快速迁移示例,用于运行通过testscenarios配置的测试,testscenarios是Robert Collins为标准的unittest框架提供的一个附加工具。我们只需要稍微努力一下,为pytest的Metafunc.parametrize构造正确的参数。

# test_scenarios.py 的内容  import pytest  def pytest_generate_tests(metafunc):  # 初始化ID列表和参数值列表  idlist = []  argvalues = []  # 遍历类属性中的场景  for scenario in getattr(metafunc.cls, 'scenarios', []):  # 添加场景ID  idlist.append(scenario[0])  # 从场景字典中提取参数名和参数值  items = scenario[1].items()  argnames = [x[0] for x in items]  # 参数名列表  argvalues.append([x[1] for x in items])  # 参数值列表  # 使用参数名和参数值列表来参数化测试  # 注意:实际上应该使用metafunc.parametrize而不是Metafunc.parametrize  # 这里也指定了scope为"class",意味着参数在类级别上共享  metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class")  # 定义场景  
scenario1 = ("basic", {"attribute": "value"})  
scenario2 = ("advanced", {"attribute": "value2"})  # 定义一个测试类,该类使用上述场景  
class TestSampleWithScenarios:  scenarios = [scenario1, scenario2]  # 测试函数,它接受从场景中传递的attribute参数  def test_demo1(self, attribute):  assert isinstance(attribute, str)  # 另一个测试函数,同样接受attribute参数  def test_demo2(self, attribute):  assert isinstance(attribute, str)

这是一个完全独立的示例,您可以使用以下命令运行它:

$ pytest test_scenarios.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 4 itemstest_scenarios.py ....                                               [100%]============================ 4 passed in 0.12s =============================

如果你只是收集测试,你也会很清楚地看到“advanced”和“basic”作为测试函数的变体:

$ pytest --collect-only test_scenarios.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 4 items<Dir parametrize.rst-200><Module test_scenarios.py><Class TestSampleWithScenarios><Function test_demo1[basic]><Function test_demo2[basic]><Function test_demo1[advanced]><Function test_demo2[advanced]>======================== 4 tests collected in 0.12s ========================

请注意,我们告诉metafunc.parametrize()你的场景值应该被视为类作用域。在pytest 2.3中,这会导致基于资源的排序。

推迟参数化资源的设置

"测试函数的参数化发生在收集时。为了优化性能,只在实际运行测试时设置昂贵的资源(如数据库连接或子进程)是一个好主意。以下是一个简单的示例,展示了如何实现这一点。此测试需要一个名为db的fixture对象:

# content of test_backends.py
import pytest  # 假设有一个db fixture,它根据某种逻辑(可能是通过参数化)返回不同类型的数据库连接  
# 这里不展示db fixture的实现,因为它可能涉及复杂的逻辑和可能的参数化  def test_db_initialized(db):  # 一个示例测试  # 检查db对象是否为DB2类的一个实例,并为此示例故意失败  if db.__class__.__name__ == "DB2":  pytest.fail("为了演示目的,故意使测试失败")

我们现在可以添加一个测试配置,该配置会生成对test_db_initialized函数的两次调用,并实现一个工厂,该工厂为实际的测试调用创建数据库对象。

# content of conftest.py
import pytest  # 使用pytest的钩子函数pytest_generate_tests来参数化测试  
def pytest_generate_tests(metafunc):  # 检查是否有任何测试函数请求了名为'db'的fixture  if "db" in metafunc.fixturenames:  # 使用parametrize方法(注意:在钩子函数中,我们使用metafunc.parametrize而不是装饰器)  # 来为'db' fixture生成两个参数值:"d1"和"d2",并设置indirect=True,  # 这意味着'db'是一个间接参数,pytest将查找同名的fixture来解析它  metafunc.parametrize("db", ["d1", "d2"], indirect=True)  # 定义两个数据库类,分别代表不同的数据库对象  
class DB1:  "一个数据库对象"  class DB2:  "另一个数据库对象"  # 定义一个fixture,它根据请求的参数返回相应的数据库对象  
@pytest.fixture  
def db(request):  # request.param包含了通过pytest_generate_tests钩子函数传递的参数值  if request.param == "d1":  return DB1()  elif request.param == "d2":  return DB2()  else:  # 如果参数不是"d1"或"d2",则抛出异常  raise ValueError("无效的内部测试配置")

pytest_generate_tests钩子函数用于在测试收集阶段为db fixture生成两个参数值:"d1"和"d2"。通过设置indirect=True,我们告诉pytest这些参数值不是直接传递给测试函数的,而是应该通过同名的fixture(即db fixture)来解析。然后,我们定义了db fixture,它接收一个request对象作为参数。request.param属性包含了通过pytest_generate_tests钩子函数传递的参数值("d1"或"d2")。根据这个参数值,db fixture返回相应的数据库对象(DB1DB2的实例)。

首先,让我们看看在收集时间(collection time)时它的样子:

$ pytest test_backends.py --collect-only  
=========================== test session starts ============================  
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y  
rootdir: /home/sweet/project  
collected 2 items  <Dir parametrize.rst-200>  # 这行可能是特定于你的输出或文档环境的,通常不会出现在实际pytest输出中  <Module test_backends.py>  <Function test_db_initialized[d1]>  # 使用DB1的测试案例  <Function test_db_initialized[d2]>  # 使用DB2的测试案例  ======================== 2 tests collected in 0.12s ========================

pytest 命令使用了 --collect-only 选项来仅收集测试,而不实际执行它们。这样做可以让你看到哪些测试案例被收集到了,而不会运行它们。

然后,当我们运行测试时:

$ pytest -q test_backends.py  
.F                                                                   [100%]  
================================= FAILURES =================================  
_________________________ test_db_initialized[d2] __________________________  db = <conftest.DB2 object at 0xdeadbeef0001>  def test_db_initialized(db):  # 一个示例测试  if db.__class__.__name__ == "DB2":  
>           pytest.fail("故意失败以演示目的")  
E           Failed: 故意失败以演示目的  test_backends.py:8: Failed  
========================= short test summary info ==========================  
FAILED test_backends.py::test_db_initialized[d2] - Failed: 故意失败以演示目的  
1 failed, 1 passed in 0.12s

输出表明,test_db_initialized函数的两个测试变体被执行了。第一个变体(使用DB1作为数据库对象)通过了测试,而第二个变体(使用DB2作为数据库对象)失败了。具体来说,当db参数是DB2的实例时,测试函数内部检查db的类名,如果发现是"DB2",则通过pytest.fail()函数故意让测试失败,并给出了“故意失败以演示目的”的错误信息。

这表明我们的db fixture函数在测试的准备阶段(setup phase)为每个数据库值(DB1DB2)创建了实例,而pytest_generate_tests钩子函数在收集阶段(collection phase)为test_db_initialized函数生成了两个相应的调用。这样,我们就能够针对不同的数据库对象运行相同的测试函数,并验证其在不同环境下的行为。

间接参数化

在参数化测试时使用indirect=True参数允许通过接收参数值的fixture来参数化测试,然后再将这些值传递给测试函数:

import pytest  @pytest.fixture  
def fixt(request):  return request.param * 3  @pytest.mark.parametrize("fixt", ["a", "b"], indirect=True)  
def test_indirect(fixt):  assert len(fixt) == 3

这种方法可以用在,例如,在fixture中在测试运行时执行更昂贵的设置操作,而不是在收集时间就运行这些设置步骤。在上面的例子中,fixt是一个fixture,它接收由parametrize装饰器通过indirect=True指定的参数值(在这个例子中是字符串"a""b"),然后对这些值进行转换(在这个例子中是将它们各自乘以3),并将结果传递给test_indirect测试函数。

对特定参数应用间接参数化

通常,参数化会使用多个参数名。可以对特定参数应用间接参数化,这可以通过将参数的名称列表或元组传递给indirect参数来实现。在下面的例子中,有一个测试函数test_indirect,它使用了两个fixture:xy

在这里,我们向indirect传递了一个列表,该列表包含了fixture x的名称。indirect参数将仅应用于这个参数,并且值"a"将被传递给相应的fixture函数。

# content of test_indirect_list.pyimport pytest  # 定义了一个作用域为函数的fixture x  
@pytest.fixture(scope="function")  
def x(request):  # 对x应用间接参数化,将接收到的param参数乘以3  return request.param * 3  # 定义了另一个作用域为函数的fixture y  
@pytest.fixture(scope="function")  
def y(request):  # 这里的y没有应用间接参数化,将直接返回接收到的param参数  return request.param * 2  # 使用parametrize装饰器对x和y进行参数化,但仅对x应用indirect  
@pytest.mark.parametrize("x, y", [("a", "b")], indirect=["x"])  
def test_indirect(x, y):  # 由于x应用了indirect,它会被传递给x fixture,经过处理后返回"aaa"  assert x == "aaa"  # y没有应用indirect,所以直接接收参数化列表中的值"b"  assert y == "b"

test_indirect函数通过parametrize装饰器接收了两个参数xy,其中只有x被标记为间接参数化(通过indirect=["x"])。因此,当测试运行时,"a"这个值会被传递给x fixture,x fixture将其乘以3后返回"aaa"给测试函数。而y fixture则直接接收了参数化列表中的"b"值。

$ pytest -v test_indirect_list.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: /home/sweet/project
collecting ... collected 1 itemtest_indirect_list.py::test_indirect[a-b] PASSED                     [100%]============================ 1 passed in 0.12s =============================

通过每个类配置参数化测试方法

在这个例子中,pytest_generate_tests 函数实现了一个类似于 Michael Foord 的 unittest 参数化器(parametrizer)的参数化方案,但使用了更少的代码。

# content of ./test_parametrize.py
import pytest  # 自定义的 pytest_generate_tests 钩子函数,它会在每个测试函数执行前被调用  
def pytest_generate_tests(metafunc):  # metafunc.cls 是指向当前测试类的引用,metafunc.function 是指向当前测试函数的引用  # 因此,我们可以从类的 params 字典中获取特定测试函数的参数列表  funcarglist = metafunc.cls.params[metafunc.function.__name__]  # 获取第一个参数集的键(即参数名)并对其排序,这确保参数名的顺序在所有的测试实例中都是一致的  argnames = sorted(funcarglist[0])  # 使用 pytest 的 parametrize 方法来参数化测试函数  # 我们为每个参数名创建一个参数列表,列表中的每个元素都是一个包含所有参数值的列表  # 这样做是为了与 pytest.mark.parametrize 的语法相匹配  metafunc.parametrize(  argnames,  # 参数名列表  [[funcargs[name] for name in argnames] for funcargs in funcarglist]  # 参数值列表  )  # 定义一个测试类  
class TestClass:  # params 字典为每个测试方法指定了多个参数集  # 每个键是测试方法的名称,每个值是一个包含多个字典的列表  # 每个字典代表测试方法的一组参数  params = {  "test_equals": [dict(a=1, b=2), dict(a=3, b=3)],  # test_equals 方法将使用这两组参数进行测试  "test_zerodivision": [dict(a=1, b=0)],  # test_zerodivision 方法将使用这组参数进行测试  }  # 定义测试方法  def test_equals(self, a, b):  assert a == b  def test_zerodivision(self, a, b):  # 预期会抛出 ZeroDivisionError 异常  with pytest.raises(ZeroDivisionError):  a / b

我们的测试生成器查找类级别的定义,该定义指定了每个测试函数应使用哪些参数集。现在让我们运行它:

$ pytest -q  
F..                                                                  [100%]  
================================= FAILURES =================================  
________________________ TestClass.test_equals[1-2] ________________________  self = <test_parametrize.TestClass object at 0xdeadbeef0002>, a = 1, b = 2  def test_equals(self, a, b):  
>       assert a == b  
E       assert 1 == 2  test_parametrize.py:21: AssertionError  
========================= short test summary info ==========================  
FAILED test_parametrize.py::TestClass::test_equals[1-2] - assert 1 == 2  
1 failed, 2 passed in 0.12s

使用多个夹具(fixtures)进行参数化

以下是一个简化的实际示例,展示了如何使用参数化测试来测试不同Python解释器之间对象的序列化。我们定义了一个test_basic_objects函数,该函数将使用三组不同的参数来运行,每组参数分别对应它的三个参数:

  • python1:第一个Python解释器,用于将对象pickle序列化到文件中。
  • python2:第二个解释器,用于从文件中pickle反序列化对象。
  • obj:要被序列化/反序列化的对象。
    """Module containing a parametrized tests testing cross-python serialization
    via the pickle module."""from __future__ import annotationsimport shutil
    import subprocess
    import textwrapimport pytestpythonlist = ["python3.9", "python3.10", "python3.11"]@pytest.fixture(params=pythonlist)
    def python1(request, tmp_path):picklefile = tmp_path / "data.pickle"return Python(request.param, picklefile)@pytest.fixture(params=pythonlist)
    def python2(request, python1):return Python(request.param, python1.picklefile)class Python:def __init__(self, version, picklefile):self.pythonpath = shutil.which(version)if not self.pythonpath:pytest.skip(f"{version!r} not found")self.picklefile = picklefiledef dumps(self, obj):dumpfile = self.picklefile.with_name("dump.py")dumpfile.write_text(textwrap.dedent(rf"""import picklef = open({str(self.picklefile)!r}, 'wb')s = pickle.dump({obj!r}, f, protocol=2)f.close()"""))subprocess.run((self.pythonpath, str(dumpfile)), check=True)def load_and_is_true(self, expression):loadfile = self.picklefile.with_name("load.py")loadfile.write_text(textwrap.dedent(rf"""import picklef = open({str(self.picklefile)!r}, 'rb')obj = pickle.load(f)f.close()res = eval({expression!r})if not res:raise SystemExit(1)"""))print(loadfile)subprocess.run((self.pythonpath, str(loadfile)), check=True)@pytest.mark.parametrize("obj", [42, {}, {1: 3}])
    def test_basic_objects(python1, python2, obj):python1.dumps(obj)python2.load_and_is_true(f"obj == {obj}")

    如果我们没有安装所有Python解释器,运行该模块时可能会跳过一些测试,否则它会运行所有组合(3个解释器与3个解释器相乘,再乘以3个要序列化/反序列化的对象)。

    . $ pytest -rs -q multipython.py
    ssssssssssss...ssssssssssss                                          [100%]
    ========================= short test summary info ==========================
    SKIPPED [12] multipython.py:65: 'python3.9' not found
    SKIPPED [12] multipython.py:65: 'python3.11' not found
    3 passed, 24 skipped in 0.12s

可选实现/导入的参数化 

如果你想比较给定API的几个不同实现的结果,你可以编写测试函数,这些函数接收已经导入的实现,并在实现无法导入/不可用时跳过测试。假设我们有一个“基础”实现,而其他(可能是优化过的)实现需要提供相似的结果。

# content of conftest.py
import pytest  # 定义一个fixture,用于导入基础模块。如果基础模块无法导入,则跳过测试。  
@pytest.fixture(scope="session")  
def basemod(request):  return pytest.importorskip("base")  # 定义一个带参数的fixture,用于导入优化模块。这里使用params参数化fixture,以支持多个优化版本。  
# 如果指定的优化模块无法导入,则跳过对应的测试。  
@pytest.fixture(scope="session", params=["opt1", "opt2"])  
def optmod(request):  return pytest.importorskip(request.param)

这是基础实现的简单函数定义 

# content of base.py
def func1():  return 1

这是一个优化版本的实现,函数返回的值略有不同。

# content of opt1.py
def func1():return 1.0001

这是测试模块,它使用前面定义的fixture来比较基础实现和优化实现的结果。

# content of test_module.pydef test_func1(basemod, optmod):# 由于func1可能返回整数或浮点数,我们使用round函数来比较两者在指定位数上的值是否相等assert round(basemod.func1(), 3) == round(optmod.func1(), 3)

如果你在运行测试时启用了跳过报告的选项:

$ pytest -rs test_module.py  
=========================== test session starts ============================  
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y  
rootdir: /home/sweet/project  
collected 2 items  test_module.py .s                                                    [100%]  ========================= short test summary info ==========================  
SKIPPED [1] test_module.py:3: could not import 'opt2': No module named 'opt2'  
======================= 1 passed, 1 skipped in 0.12s =======================

你会看到我们没有opt2模块,因此test_func1的第二次测试运行被跳过了。这里有几个注意事项:

  1. conftest.py文件中的fixture函数是“会话范围”的:因为我们不需要多次导入

  2. 如果有多个测试函数和一个被跳过的导入:在测试报告中,你会看到[1]计数增加,表示有一个测试被跳过了。

  3. 在测试函数上使用 @pytest.mark.parametrize 风格的参数化:你还可以在测试函数上使用@pytest.mark.parametrize来参数化输入/输出值。

为单个参数化测试设置标记或测试ID

使用pytest.param来为单个参数化测试应用标记或设置测试ID。例如:

# test_pytest_param_example.py 文件内容  
import pytest  @pytest.mark.parametrize(  "test_input,expected",  [  ("3+5", 8),  pytest.param("1+7", 8, marks=pytest.mark.basic),  pytest.param("2+4", 6, marks=pytest.mark.basic, id="basic_2+4"),  pytest.param(  "6*9", 42, marks=[pytest.mark.basic, pytest.mark.xfail], id="basic_6*9"  ),  ],  
)  
def test_eval(test_input, expected):  assert eval(test_input) == expected

在这个例子中,我们有4个参数化测试。除了第一个测试外,我们使用自定义标记basic来标记其余三个参数化测试。对于第四个测试,我们还使用了内置标记xfail来表示这个测试预期会失败。为了更明确,我们还为一些测试设置了测试ID。

  • 第一个测试没有特别的标记或ID。
  • 第二个测试使用了pytest.param并附加了pytest.mark.basic标记。
  • 第三个测试同样使用了pytest.param并附加了pytest.mark.basic标记,同时还设置了测试ID为"basic_2+4"以提供更清晰的测试描述。
  • 第四个测试则更加复杂,它不仅附加了pytest.mark.basicpytest.mark.xfail两个标记,还设置了测试ID为"basic_6*9",以明确表示这个测试的预期失败情况及其对应的输入。

然后,使用详细模式并仅包含带有basic标记的测试来运行pytest:

$ pytest -v -m basic  
=========================== test session starts ============================  
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python  
cachedir: .pytest_cache  
rootdir: /home/sweet/project  
collecting ... collected 24 items / 21 deselected / 3 selected  test_pytest_param_example.py::test_eval[1+7-8] PASSED                [ 33%]  
test_pytest_param_example.py::test_eval[basic_2+4] PASSED            [ 66%]  
test_pytest_param_example.py::test_eval[basic_6*9] XFAIL             [100%]  =============== 2 passed, 21 deselected, 1 xfailed in 0.12s ================

结果如下:

  • 总共收集了四个测试。
  • 一个测试因为没有basic标记而被排除。
  • 三个带有basic标记的测试被选中并执行。
  • 测试test_eval[1+7-8]通过了,但其名称是自动生成的,可能会让人容易混淆。
  • 测试test_eval[basic_2+4]通过了。
  • 测试test_eval[basic_6*9]预期会失败,并且确实失败了。这是因为它被标记为xfail

参数化条件异常抛出

使用pytest.raises()pytest.mark.parametrize装饰器来编写参数化测试,其中一些测试会抛出异常,而另一些则不会。

contextlib.nullcontext可用于测试那些不期望抛出异常但应产生某些值的用例。这个值作为enter_result参数给出,它将作为with语句的目标(在以下示例中为e)可用。

这样,您可以在测试函数中统一使用with语句来处理所有情况,即使某些情况不期望抛出异常也能正常工作。contextlib.nullcontextwith块执行时不会做任何操作,只是简单地返回您通过enter_result参数提供的值(如果有的话),从而允许测试逻辑保持一致性和简洁性。

from contextlib import nullcontextimport pytest@pytest.mark.parametrize("example_input,expectation",[(3, nullcontext(2)),(2, nullcontext(3)),(1, nullcontext(6)),(0, pytest.raises(ZeroDivisionError)),],
)
def test_division(example_input, expectation):"""Test how much I know division."""with expectation as e:assert (6 / example_input) == e

在上面的示例中,前三个测试用例应该能够顺利运行而不抛出任何异常,而第四个测试用例则应该抛出一个ZeroDivisionError异常,这是pytest所期望的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3269501.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

2. 卷积神经网络无法绕开的神——LeNet

卷积神经网络无法绕开的大神——LeNet 1. 基本架构2. LeNet 53. LeNet 5 代码 1. 基本架构 特征抽取模块可学习的分类器模块 2. LeNet 5 LeNet 5: 5 表示的是5个核心层&#xff0c;2个卷积层&#xff0c;3个全连接层.核心权重层&#xff1a;卷积层、全连接层、循环层&#xff…

093、Python操作Excel生成统计图表

在Excel里做统计表是我们经常会做的一件事情。我们也可以通过编程的方式操作Excel生成统计图表。 下面是官方的一个很有参考价值的案例&#xff1a; from openpyxl import Workbook from openpyxl.chart import BarChart, Reference from copy import deepcopywb Workbook(w…

生活实用英语口语“拆迁”用英文怎么说?柯桥成人学英语到蓝天广场

● 1. “拆迁”英语怎么说&#xff1f; ● 01. 其实国外也有拆迁 但国外的拆迁&#xff0c;只管拆 不管安置&#xff0c;你爱去哪去哪 英文可以说 housing removal 02. 但我们中国的“拆迁” 既管“拆”也管“迁” &#xff08;还是中国人幸福~&#xff09; 英文可以说 housin…

车载录像机给公交公司管理带来哪些好处

一、引言 随着社会的快速发展&#xff0c;公共交通日益成为人们出行的主要方式之一。对于公交公司而言&#xff0c;如何有效管理车辆及司乘人员&#xff0c;确保行车安全、服务质量以及乘客的合法权益&#xff0c;成为一项重要的任务。本文将从以下几个方面详细阐述管理效果的…

排查C++软件异常的常见思路与方法(实战经验总结)

目录 1、概述 2、常用的C++异常排查思路与方法 2.1、IDE调试 2.1.1、Debug和Release下的调试 2.1.2、VS附加到进程调试 2.1.3、Windbg附加到进程调试 2.2、添加日志打印 2.3、分块注释代码 2.4、数据断点 2.5、历史版本比对法 2.6、Windbg静态分析与动态调试 2.6.1…

7.24 补题

C 小w和大W的决斗 链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 题目描述 小w和大W为了比出谁更聪明。决定进行一场游戏。游戏内容如下: 两人轮流操作&#xff0c;小w先进行操作&#xff0c;每次操作可以选择下列两个其一: 选择数组中的一…

websocket通信问题排查思路

websocket通信问题排查思路 一、websocket连接成功&#xff0c;但数据完全推不过来。 通过抓包发现&#xff0c;是回包时间太长超过了1分钟导致的。这种通常是推送数据的线程有问题导致的。 正常抓包的情况如下&#xff1a; 二、大量数据可以正常推送成功&#xff0c;不定时…

【机器学习】机器学习之多变量线性回归-Multiple_Variable_Soln

引言 扩展数据结构和之前开发的例程&#xff0c;以支持多个特征。有几个例程被更新&#xff0c;使得实验看起来有些冗长&#xff0c;但实际上只是对之前的例程进行了小的调整&#xff0c;因此快速回顾是可行的 文章目录 引言一、多变量线性回归1.1 目标1.2 工具 二、问题陈述2.…

【因数之和】python求解方法

输入两个整数A和B&#xff0c;求A的B次方的因子和&#xff0c;结果对1000000007取模。 def mod_exp(base, exp, mod):result 1while exp > 0:if exp % 2 1:result (result * base) % modbase (base * base) % modexp // 2return resultdef sum_of_factors(n):total 0…

【无标题】shell脚本的基本命令+编写shell脚本

shell脚本 一.shell基础 1.shell概念 2.shell脚本 3.shell脚本编写注意事项 二.编写shell脚本 1.编写一个helloworld脚本&#xff0c;运行脚本 [rootshell ~]# vim helloworld.sh #!/bin/bash //声明 echo "hello world!" ls -lh /etc/ 运行脚本(四种方式)&…

c/c++的内存管理(超详细)

一、c/c的内存分布 这是操作系统中对于内存的划分&#xff1a; 我们重点掌握以下几个区域即可&#xff1a; 1.栈 (调用函数会建立栈帧) 2.堆(动态开辟的空间) 3.数据段(静态区)&#xff1a;存放静态变量以及全局变量 4.代码段 (常量区) 先来看看一个题目&#xff1a; int…

[物联网专题] RS485继电器输出之Modbus控制流程和时间优化分析

在工控领域&#xff0c;往往需要大量的输入信号和输出控制信号&#xff0c;以接收各种传感信号和产生输出控制动作。由于PLC的输出触点数量有限&#xff0c;或者因为更多输出触点的PLC价格昂贵&#xff0c;性价比并不高。为了解决这个矛盾&#xff0c;基于MODBUS协议的继电器IO…

数据结构:基础概念

一、相关概念 概念 相互之间存在一种或多种特定关系的数据元素的集合。 逻辑结构 集合&#xff1a;所有数据在同一个集合中&#xff0c;关系平等。 线性&#xff1a;数据和数据之间是一对一的关系 树&#xff1a; 一对多 图&#xff1a;多对多 物理结构(在内存当中的存储关系)…

AC695x BLE OTA调试

SDK版本&#xff1a;AC695N_soundbox_sdk_release_3.1.0AC695x SDK支持BLE OTA升级&#xff0c;使用杰理公版APP升级即可。SDK需要做一些调整&#xff0c;板级文件需要增加如下配置&#xff0c;使能OTA升级 #define TCFG_APP_BT_EN 1#define APP_UPDATE_EN …

Three.js动效(第09辑):令人瞠目结舌的交互效果,沉浸式体验

three.js能够实现各种3D动态效果&#xff0c;不禁有小伙伴问了&#xff0c;实现这些效果到底有什么意思&#xff0c;其实最大的意义就是给用户沉浸式的体验&#xff0c;瞬间专注用户注意力。 Three.js能够带来以下沉浸式体验&#xff1a; 3D虚拟现实体验&#xff1a; 使用Th…

MATLAB-bode图编程

num[1 1];den [2 1];tf(num,den)bode(tf(num,den));hold on

PHP8.3.9安装记录,Phpmyadmin访问提示缺少mysqli

ubuntu 22.0.4 腾讯云主机 下载好依赖 sudo apt update sudo apt install -y build-essential libxml2-dev libssl-dev libcurl4-openssl-dev pkg-config libbz2-dev libreadline-dev libicu-dev libsqlite3-dev libwebp-dev 下载php8.3.9安装包 nullhttps://www.php.net/d…

Bert文本分类和命名实体的模型架构剖析

文章目录 介绍Bert模型架构损失计算方式BertForSequenceClassificationBertForTokenClassification Bert 输出结果剖析例子 参考资料 介绍 文本分类&#xff1a;给一句文本分类&#xff1b; 实体识别&#xff1a;从一句文本中&#xff0c;识别出其中的实体&#xff1b; 做命名…

由于误操作原因丢失了照片?6 款 Android 照片恢复应用程序可能有帮助

由于意外删除&#xff0c;软件故障&#xff0c;系统崩溃&#xff0c;恢复出厂设置或任何其他原因&#xff0c;您可能会丢失Android手机中的照片。无论如何&#xff0c;您仍然有很大的机会借助Android照片恢复应用程序恢复照片。有很多应用程序提供恢复支持&#xff0c;但并非所…

zeal 开发者离线文档工具

zeal是一款程序开发者不可或缺的离线文档查看器 下载地址 官网地址&#xff1a; windows版csdn下载&#xff1a;https://zealdocs.org/download.html#windows windows版官网下载&#xff1a;https://zealdocs.org/download.html#windows 下载压缩版&#xff0c;解压即用相对…