欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 美景 > pytest8.x版本 中文使用文档-------19.测试夹具Fixtures参考

pytest8.x版本 中文使用文档-------19.测试夹具Fixtures参考

2024/10/22 21:51:23 来源:https://blog.csdn.net/2301_76646967/article/details/140540151  浏览:    关键词:pytest8.x版本 中文使用文档-------19.测试夹具Fixtures参考

另请参阅

关于夹具

另请参阅

如何使用固定装置

内置的固定夹具

在Pytest中,夹具(Fixtures)是通过@pytest.fixture装饰器来定义的。Pytest提供了一些非常有用的内置夹具。

  • capfd:以文本形式捕获文件描述符1(标准输出stdout)和文件描述符2(标准错误stderr)的输出。这对于验证程序是否按预期输出到控制台非常有用。

  • capfdbinary:以字节形式捕获文件描述符1和文件描述符2的输出。这在处理二进制数据时特别有用,比如处理非文本格式的输出。

  • caplog:控制日志并记录日志条目。这允许你在测试过程中捕获日志输出,以便验证日志记录是否按预期工作。这对于调试和确保程序在不同条件下生成正确的日志信息特别有帮助。

  • capsys:以文本形式捕获sys.stdoutsys.stderr的输出。这与capfd类似,但更直接地针对Python的sys模块中的标准输出和错误输出流。

  • capsysbinary:以字节形式捕获sys.stdoutsys.stderr的输出。这在你需要捕获和处理以二进制形式输出的数据时非常有用。

  • cache:跨多个pytest运行存储和检索值。这对于缓存测试结果、配置或其他需要跨测试会话持久化的数据非常有用。通过减少重复计算或重新加载数据,它可以显著提高测试的效率。

  • doctest_namespace:向doctest命名空间中注入一个字典。这允许你在doctest测试中使用额外的变量或函数,而无需在测试文档字符串中直接定义它们。

  • monkeypatch:临时修改类、函数、字典、os.environ环境变量以及其他对象。这对于测试对外部依赖(如环境变量、外部库函数等)的依赖非常有用,因为它允许你在不修改实际代码的情况下模拟这些依赖。

  • pytestconfig:访问配置值、插件管理器和插件钩子。这允许你在测试或夹具中访问pytest的配置选项,以及调用或修改插件的行为。

  • record_property:向测试添加额外的属性。这可以用于在测试报告中包含额外的信息,例如测试的性能指标、使用的资源等。

  • record_testsuite_property:向测试套件添加额外的属性。这与record_property类似,但属性被添加到整个测试套件而不是单个测试上。

  • recwarn:记录由测试函数发出的警告。这对于验证代码是否在特定条件下发出预期的警告非常有用。

  • request:提供有关正在执行的测试函数的信息。这包括测试函数的名称、其所属的测试类(如果有的话)、测试参数等。

  • testdir:提供一个临时测试目录,以帮助运行和测试pytest插件。这对于开发pytest插件特别有用,因为它允许你在隔离的环境中测试插件的行为。

  • tmp_path:为每个测试函数提供一个唯一的临时目录,并返回一个pathlib.Path 对象。这是tmpdir的现代替代品,使用pathlib库提供的更现代、更直观的路径操作API。

  • tmp_path_factory:创建会话范围的临时目录,并返回pathlib.Path 对象。这允许你在测试会话期间创建和管理多个临时目录,而无需在每个测试函数中单独创建它们。

  • tmpdir(已弃用,建议使用tmp_path):为每个测试函数提供一个唯一的临时目录,并返回一个 py.path.local 对象。这是tmp_path的前身,但在较新版本的pytest中已被tmp_path取代。

  • tmpdir_factory(已弃用,建议使用tmp_path_factory):创建会话范围的临时目录,并返回py.path.local对象。与tmp_path_factory类似,但使用py.path.local对象而不是pathlib.Path对象。这同样在较新版本的pytest中已被tmp_path_factory.取代。

夹具可用性

夹具的可用性是从测试的角度来确定的。一个夹具只有在它所定义的范围内才能被测试请求使用。如果夹具是在某个类内部定义的,那么只有该类内部的测试才能请求使用它。但是,如果夹具是在模块的全局范围内定义的,那么该模块内的每一个测试,即使它们定义在类内部,也可以请求使用这个夹具。

类似地,一个测试也只有在与自动使用夹具(autouse fixture)处于相同的作用域时,才会受到该自动使用夹具的影响(请参考“自动使用夹具在其作用域内首先执行”)。

此外,一个夹具也可以请求使用任何其他夹具,无论这些夹具是在哪里定义的,只要请求它们的测试能够看到所有涉及的夹具即可。

例如,这里有一个测试文件,其中有一个夹具(outer)试图从它未定义的作用域中请求另一个夹具(inner):

from __future__ import annotationsimport pytest@pytest.fixture
def order():return []@pytest.fixture
def outer(order, inner):order.append("outer")class TestOne:@pytest.fixturedef inner(self, order):order.append("one")def test_order(self, order, outer):assert order == ["one", "outer"]class TestTwo:@pytest.fixturedef inner(self, order):order.append("two")def test_order(self, order, outer):assert order == ["two", "outer"]

从测试的角度来看,它们没有问题地看到各自所依赖的每个夹具(fixture):

所以,当它们运行时,outer 会没有问题地找到 inner,因为 pytest 是从测试的角度进行搜索的。

注意

一个夹具(fixture)被定义的作用域(scope)不会影响它被实例化的顺序:顺序是由这里描述的逻辑决定的。

conftest.py: 在多个文件间共享夹具

conftest.py 文件提供了一种为整个目录提供夹具的方式。在 conftest.py 中定义的夹具可以被该包内的任何测试使用,而无需显式导入(pytest 会自动发现它们)。

你可以有多个包含测试的嵌套目录/包,每个目录都可以有自己的 conftest.py 文件及其自己的夹具,这些夹具会添加到父目录中的 conftest.py 文件提供的夹具之上。这种方式允许你根据目录结构组织测试夹具,使得测试更加模块化和易于管理。

例如,给定以下测试文件结构:

tests/__init__.pyconftest.py# content of tests/conftest.pyimport pytest@pytest.fixturedef order():return []@pytest.fixturedef top(order, innermost):order.append("top")test_top.py# content of tests/test_top.pyimport pytest@pytest.fixturedef innermost(order):order.append("innermost top")def test_order(order, top):assert order == ["innermost top", "top"]subpackage/__init__.pyconftest.py# content of tests/subpackage/conftest.pyimport pytest@pytest.fixturedef mid(order):order.append("mid subpackage")test_subpackage.py# content of tests/subpackage/test_subpackage.pyimport pytest@pytest.fixturedef innermost(order, mid):order.append("innermost subpackage")def test_order(order, top):assert order == ["mid subpackage", "innermost subpackage", "top"]

作用域的范围可以这样可视化:

目录成为了它们自己的一种作用域,其中在该目录的 conftest.py 文件中定义的夹具在该整个作用域内都是可用的。

测试被允许向上搜索(走出一个圆圈)以查找夹具,但永远不能向下(走进一个圆圈)继续搜索。因此,tests/subpackage/test_subpackage.py::test_order 能够找到在 tests/subpackage/test_subpackage.py 中定义的最内层夹具,但无法找到在 tests/test_top.py 中定义的夹具,因为它需要向下一级(走进一个圆圈)才能找到它。这里的“圆圈”是一个比喻,用于形象地表示目录结构中的层级和作用域的限制。

测试找到的第一个夹具就是将要使用的夹具,因此,如果你需要为特定作用域更改或扩展某个夹具的功能,可以覆盖该夹具fixtures can be overridden 。

你还可以使用 conftest.py 文件来实现针对每个目录的本地插件local per-directory plugins。

来自第三方插件的夹具

然而,夹具并不一定要在这个结构中定义才能供测试使用。它们也可以由已安装的第三方插件提供,这是许多pytest插件的工作方式。只要这些插件已安装,你就可以在测试套件中的任何位置请求它们提供的夹具。

由于它们是从你的测试套件结构之外提供的,因此第三方插件并不真正像conftest.py文件和测试套件中的目录那样提供作用域。因此,pytest会按照之前解释的那样,通过作用域向外搜索夹具,最后才到达插件中定义的夹具。

例如,给定以下文件结构:

tests/__init__.pyconftest.py# content of tests/conftest.pyimport pytest@pytest.fixturedef order():return []subpackage/__init__.pyconftest.py# content of tests/subpackage/conftest.pyimport pytest@pytest.fixture(autouse=True)def mid(order, b_fix):order.append("mid subpackage")test_subpackage.py# content of tests/subpackage/test_subpackage.pyimport pytest@pytest.fixturedef inner(order, mid, a_fix):order.append("inner subpackage")def test_order(order, inner):assert order == ["b_fix", "mid subpackage", "a_fix", "inner subpackage"]

如果已安装插件 plugin_a 并提供了夹具 a_fix,同时已安装插件 plugin_b 并提供了夹具 b_fix,那么测试搜索夹具的过程将如下所示:

pytest 会在首先搜索 tests/ 目录下的作用域(scopes)中的 a_fix 和 b_fix 之后,才会在插件中搜索它们。

Fixture 实例化顺序

当 pytest 想要执行一个测试时,一旦它知道了将要执行哪些 fixtures,它就必须确定这些 fixtures 的执行顺序。为了做到这一点,pytest 会考虑以下三个因素:

  1. 作用域(Scope)
    • Fixtures 的作用域决定了它们的生命周期和可访问性范围。pytest 会根据作用域(如 function、class、module、session)来确定 fixtures 的实例化顺序。通常,作用域较小的 fixtures(如 function)会在作用域较大的 fixtures(如 session)之后实例化,因为作用域较大的 fixtures 需要在更多测试或测试集合之间保持状态。
  2. 依赖关系(Dependencies)
    • 如果一个 fixture 依赖于另一个 fixture(即一个 fixture 在其实现中请求了另一个 fixture),那么被依赖的 fixture 将首先被实例化。这种依赖关系决定了 fixtures 的实例化顺序。
  3. 自动使用(Autouse)
    • 自动使用的 fixtures(即标记为 autouse=True 的 fixtures)会在非自动使用的 fixtures 之前实例化,因为自动使用的 fixtures 被设计为在测试执行之前自动设置测试环境。

Fixture 或测试的名称、它们被定义的位置、它们被定义的顺序,以及 fixtures 被请求的顺序,除了偶然的巧合外,对执行顺序没有直接影响。虽然 pytest 会尽力确保这种偶然的一致性在每次运行之间保持不变,但这并不是可以依赖的。如果你想要控制顺序,最安全的方法是依赖上述三个因素,并确保依赖关系明确建立。

首先执行高作用域的fixture

在对fixture的函数请求中,高作用域的请求(如session)在低作用域的fixture(如函数或类)之前执行。

这里有一个例子:

from __future__ import annotationsimport pytest@pytest.fixture(scope="session")
def order():return []@pytest.fixture
def func(order):order.append("function")@pytest.fixture(scope="class")
def cls(order):order.append("class")@pytest.fixture(scope="module")
def mod(order):order.append("module")@pytest.fixture(scope="package")
def pack(order):order.append("package")@pytest.fixture(scope="session")
def sess(order):order.append("session")class TestClass:def test_order(self, func, cls, mod, pack, sess, order):assert order == ["session", "package", "module", "class", "function"]

测试会通过,因为作用域更大的 fixtures 会首先执行。

执行顺序分解如下:

相同顺序的 Fixtures 基于依赖关系执行

当一个 fixture 请求另一个 fixture 时,被请求的 fixture 会首先执行。因此,如果 fixture a 请求了 fixture b,那么 fixture b 将首先执行,因为 a 依赖于 b 并且没有 b 就无法工作。即使 a 不需要 b 的结果,如果它需要确保在 b 之后执行,它仍然可以请求 b。

例如:

from __future__ import annotationsimport pytest@pytest.fixture
def order():return []@pytest.fixture
def a(order):order.append("a")@pytest.fixture
def b(a, order):order.append("b")@pytest.fixture
def c(b, order):order.append("c")@pytest.fixture
def d(c, b, order):order.append("d")@pytest.fixture
def e(d, b, order):order.append("e")@pytest.fixture
def f(e, order):order.append("f")@pytest.fixture
def g(f, c, order):order.append("g")def test_order(g, order):assert order == ["a", "b", "c", "d", "e", "f", "g"]

 如果我们绘制出各个 fixture 之间的依赖关系,我们会得到类似这样的东西:

每个 fixture 提供的规则(即每个 fixture 必须跟随哪个或哪些 fixture)足够全面,以至于可以简化为这样:

这些请求必须提供足够的信息,以便 pytest 能够确定一个清晰、线性的依赖链,并因此为给定的测试确定操作顺序。如果存在任何歧义,并且操作顺序可以以多种方式解释,那么你应该假设 pytest 可以在任何时候选择这些解释中的任何一个。为了避免这种不确定性,建议在编写测试时明确指定 fixture 之间的依赖关系,以确保测试的可预测性和可重复性。

例如,如果 d 没有请求 c,即图表看起来像这样:

因为除了 g 之外没有其他任何东西请求 c,并且 g 也请求了 f,所以现在不清楚 c 应该在 f、e 或 d 之前还是之后执行。为 c 设置的唯一规则是它必须在 b 之后和 g 之前执行。

在这种情况下,pytest 不知道 c 应该放在哪里,所以应该假设它可以在 g 和 b 之间的任何位置。

这并不一定是坏事,但它是需要记住的一件事。如果它们执行的顺序可能会影响测试所针对的行为,或者可能以其他方式影响测试的结果,那么应该以一种允许 pytest 线性化/“展平”该顺序的方式明确定义该顺序。

自动使用的 fixture 在其作用域内首先执行

自动使用的 fixture 被假定为适用于可能引用它们的每个测试,因此它们在该作用域内的其他 fixture 之前执行。被自动使用的 fixture 请求的 fixture 对于实际自动使用的 fixture 所适用的测试而言,它们本身也有效地成为了自动使用的 fixture。

因此,如果 fixture a 是自动使用的,而 fixture b 不是,但 fixture a 请求了 fixture b,那么 fixture b 对于 a 所适用的测试而言也将有效地成为自动使用的 fixture。

在最后一个例子中,如果 d 没有请求 c,那么图表就会变得不清晰。但如果 c 是自动使用的,那么由于 c 依赖于 b 和 a,b 和 a 也将有效地成为自动使用的 fixture。结果,它们都将在该作用域内的非自动使用的 fixture 之上执行。

 所以,如果测试文件看起来像这样:

from __future__ import annotationsimport pytest@pytest.fixture
def order():return []@pytest.fixture
def a(order):order.append("a")@pytest.fixture
def b(a, order):order.append("b")@pytest.fixture(autouse=True)
def c(b, order):order.append("c")@pytest.fixture
def d(b, order):order.append("d")@pytest.fixture
def e(d, order):order.append("e")@pytest.fixture
def f(e, order):order.append("f")@pytest.fixture
def g(f, c, order):order.append("g")def test_order_and_g(g, order):assert order == ["a", "b", "c", "d", "e", "f", "g"]

 图形看起来是这样的:

因为现在 c 可以被放在 d 的上方在图中,pytest 可以再次将图线性化为这样:

在这个例子中,c 使得 b 和 a 也有效地成为了自动使用的 fixture。

但是要小心使用autouse fixture,因为自动使用的 fixture 会自动为每个能够访问到它的测试执行,即使这些测试没有请求它。例如,考虑以下文件: 

from __future__ import annotationsimport pytest@pytest.fixture(scope="class")
def order():return []@pytest.fixture(scope="class", autouse=True)
def c1(order):order.append("c1")@pytest.fixture(scope="class")
def c2(order):order.append("c2")@pytest.fixture(scope="class")
def c3(order, c1):order.append("c3")class TestClassWithC1Request:def test_order(self, order, c1, c3):assert order == ["c1", "c3"]class TestClassWithoutC1Request:def test_order(self, order, c2):assert order == ["c1", "c2"]

 即使 TestClassWithoutC1Request 类中的任何测试都没有请求 c1c1 仍然会为该类中的测试执行:

但是,仅仅因为一个自动使用的 fixture 请求了一个非自动使用的 fixture,并不意味着这个非自动使用的 fixture 在所有它可以应用的上下文中都变成了自动使用的 fixture。它实际上只在真实的自动使用的 fixture(即请求了非自动使用的 fixture 的那个)可以应用的上下文中有效地变成了自动使用的 fixture。果一个自动使用的 fixture 请求了另一个 fixture,那么被请求的 fixture 只会对那个自动使用的 fixture 所适用的测试或上下文自动执行。它不会自动扩展到其他没有直接请求它的测试或上下文中。

例如,看看这个测试文件:

from __future__ import annotationsimport pytest@pytest.fixture
def order():return []@pytest.fixture
def c1(order):order.append("c1")@pytest.fixture
def c2(order):order.append("c2")class TestClassWithAutouse:@pytest.fixture(autouse=True)def c3(self, order, c2):order.append("c3")def test_req(self, order, c1):assert order == ["c2", "c3", "c1"]def test_no_req(self, order):assert order == ["c2", "c3"]class TestClassWithoutAutouse:def test_req(self, order, c1):assert order == ["c1"]def test_no_req(self, order):assert order == []

 它会分解成这样:

在 TestClassWithAutouse 类中的 test_req 和 test_no_req 测试中,由于 c3 是一个自动使用的 fixture,它请求了 c2,这实际上使得 c2 在这些测试的上下文中也表现得像是一个自动使用的 fixture。这就是为什么 c2 和 c3 都会被这两个测试执行,即使它们没有被显式请求,以及为什么 c2 和 c3 会在 test_req 测试中先于 c1 执行。

但是,这并没有使 c2 成为一个在所有上下文中都自动使用的 fixture。如果这样做的话,那么 c2 也会在 TestClassWithoutAutouse 类中的测试中被执行,因为这些测试(如果它们想的话)可以引用 c2。然而,事实并非如此,因为从 TestClassWithoutAutouse 类中的测试的角度来看,c2 并不是一个自动使用的 fixture,因为它们无法看到 c3c3 是在 TestClassWithAutouse 类的作用域内定义的,并且只有在这个作用域内,它才作为一个自动使用的 fixture 来请求 c2,从而使 c2 在这个特定的作用域内表现得像是一个自动使用的 fixture。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com