Fixtures 参考¶
另请参见
另请参见
内置 fixtures¶
Fixtures 使用 @pytest.fixture 装饰器定义。Pytest 有几个有用的内置 fixture
capfd将输出到文件描述符
1和2的内容捕获为文本。capfdbinary将输出到文件描述符
1和2的内容捕获为字节。caplog控制日志记录并访问日志条目。
capsys将输出到
sys.stdout和sys.stderr的内容捕获为文本。capteesys以与
capsys相同的方式捕获,但也会根据--capture=传递文本。capsysbinary将输出到
sys.stdout和sys.stderr的内容捕获为字节。cache在 pytest 运行期间存储和检索值。
doctest_namespace提供一个注入到 doctest 命名空间的字典。
monkeypatch临时修改类、函数、字典、
os.environ和其他对象。pytestconfig访问配置值、插件管理器和插件钩子。
subtests允许在测试函数中声明子测试。
record_property为测试添加额外属性。
record_testsuite_property为测试套件添加额外属性。
recwarn记录测试函数发出的警告。
request提供有关正在执行的测试函数的信息。
testdir提供一个临时测试目录,以帮助运行和测试 pytest 插件。
tmp_path为每个测试函数提供一个唯一的临时目录的
pathlib.Path对象。tmp_path_factory创建会话范围的临时目录并返回
pathlib.Path对象。tmpdir为每个测试函数提供一个唯一的临时目录的 py.path.local 对象;已被
tmp_path替换。tmpdir_factory创建会话范围的临时目录并返回
py.path.local对象;已被tmp_path_factory替换。
Fixture 可用性¶
Fixture 的可用性从测试的角度确定。Fixture 仅在测试可以请求的范围内可用。如果 fixture 定义在类内部,则只能由该类内部的测试请求。但如果 fixture 定义在模块的全局范围内,则该模块中的每个测试,即使定义在类内部,也可以请求它。
同样,如果测试与 autouse fixture 定义在同一范围内,则该测试也只能受 autouse fixture 的影响(参见 Autouse fixture 在其范围内首先执行)。
Fixture 还可以请求任何其他 fixture,无论它定义在哪里,只要请求它们的测试能够看到所有相关的 fixture。
例如,这是一个测试文件,其中包含一个 fixture (outer),它从未定义的范围请求一个 fixture (inner)
from __future__ import annotations
import pytest
@pytest.fixture
def order():
return []
@pytest.fixture
def outer(order, inner):
order.append("outer")
class TestOne:
@pytest.fixture
def inner(self, order):
order.append("one")
def test_order(self, order, outer):
assert order == ["one", "outer"]
class TestTwo:
@pytest.fixture
def inner(self, order):
order.append("two")
def test_order(self, order, outer):
assert order == ["two", "outer"]
从测试的角度来看,它们能够毫无问题地看到它们所依赖的每个 fixture
因此,当它们运行时,outer 会毫无问题地找到 inner,因为 pytest 是从测试的角度进行搜索的。
注意
Fixture 定义的范围对其实例化顺序没有影响:顺序由 此处 描述的逻辑强制执行。
conftest.py:在多个文件之间共享 fixture¶
conftest.py 文件是为整个目录提供 fixture 的方式。在 conftest.py 中定义的 fixture 可以被该包中的任何测试使用,而无需导入它们(pytest 会自动发现它们)。
您可以有多个包含测试的嵌套目录/包,每个目录都可以有自己的 conftest.py 及其自己的 fixture,这些 fixture 会添加到父目录中 conftest.py 文件提供的 fixture 中。
例如,给定这样的测试文件结构
tests/
__init__.py
conftest.py
# content of tests/conftest.py
import pytest
@pytest.fixture
def order():
return []
@pytest.fixture
def top(order, innermost):
order.append("top")
test_top.py
# content of tests/test_top.py
import pytest
@pytest.fixture
def innermost(order):
order.append("innermost top")
def test_order(order, top):
assert order == ["innermost top", "top"]
subpackage/
__init__.py
conftest.py
# content of tests/subpackage/conftest.py
import pytest
@pytest.fixture
def mid(order):
order.append("mid subpackage")
test_subpackage.py
# content of tests/subpackage/test_subpackage.py
import pytest
@pytest.fixture
def innermost(order, mid):
order.append("innermost subpackage")
def test_order(order, top):
assert order == ["mid subpackage", "innermost subpackage", "top"]
范围的边界可以像这样可视化
目录成为它们自己的一种范围,在该目录中的 conftest.py 文件中定义的 fixture 在整个范围内可用。
测试被允许向上搜索(走出圈子)寻找 fixture,但绝不能向下(进入圈子)继续搜索。因此,tests/subpackage/test_subpackage.py::test_order 能够找到在 tests/subpackage/test_subpackage.py 中定义的 innermost fixture,但在 tests/test_top.py 中定义的 fixture 对其不可用,因为它必须向下移动一个级别(进入圈子)才能找到它。
测试找到的第一个 fixture 是将被使用的 fixture,因此,如果需要更改或扩展某个特定范围的功能,可以覆盖 fixture。
您还可以使用 conftest.py 文件来实现 本地按目录插件。
来自第三方插件的 fixture¶
Fixture 不必以这种结构定义才能供测试使用。它们也可以由已安装的第三方插件提供,许多 pytest 插件就是这样运作的。只要这些插件已安装,它们提供的 fixture 就可以从测试套件中的任何位置请求。
由于它们是从测试套件结构外部提供的,第三方插件并不像 conftest.py 文件和测试套件中的目录那样提供范围。因此,pytest 将如前所述通过范围逐步向外搜索 fixture,只有在 *最后* 才搜索插件中定义的 fixture。
例如,给定以下文件结构
tests/
__init__.py
conftest.py
# content of tests/conftest.py
import pytest
@pytest.fixture
def order():
return []
subpackage/
__init__.py
conftest.py
# content of tests/subpackage/conftest.py
import pytest
@pytest.fixture(autouse=True)
def mid(order, b_fix):
order.append("mid subpackage")
test_subpackage.py
# content of tests/subpackage/test_subpackage.py
import pytest
@pytest.fixture
def 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 并提供了 fixture a_fix,并且安装了 plugin_b 并提供了 fixture b_fix,那么测试对 fixture 的搜索将如下所示
pytest 将在 tests/ 内部的范围中首先搜索 a_fix 和 b_fix 之后,才在插件中搜索它们。
Fixture 实例化顺序¶
当 pytest 想要执行测试时,一旦它知道将要执行哪些 fixture,它就必须确定它们的执行顺序。为此,它会考虑 3 个因素
范围
依赖关系
autouse
Fixture 或测试的名称、它们的定义位置、它们的定义顺序以及 fixture 的请求顺序对执行顺序没有影响,除了巧合。虽然 pytest 会努力确保这些巧合在不同运行之间保持一致,但这不应该被依赖。如果您想控制顺序,最安全的方法是依赖这 3 点并确保明确建立依赖关系。
更高范围的 fixture 首先执行¶
在对 fixture 的函数请求中,更高范围的 fixture(例如 session)在较低范围的 fixture(例如 function 或 class)之前执行。
这是一个例子
from __future__ import annotations
import 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"]
测试将通过,因为范围更大的 fixture 首先执行。
顺序分解如下
相同顺序的 fixture 根据依赖关系执行¶
当一个 fixture 请求另一个 fixture 时,另一个 fixture 首先执行。因此,如果 fixture a 请求 fixture b,则 fixture b 将首先执行,因为 a 依赖于 b 并且没有它无法操作。即使 a 不需要 b 的结果,如果它需要确保在 b 之后执行,它仍然可以请求 b。
例如
from __future__ import annotations
import 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 之后)足够全面,可以将其平铺为这样
必须通过这些请求提供足够的信息,以便 pytest 能够找出清晰的线性依赖链,从而找出给定测试的操作顺序。如果存在任何歧义,并且操作顺序可以有多种解释,则应假定 pytest 可以在任何时候选择其中任何一种解释。
例如,如果 d 没有请求 c,即图表将如下所示
因为除了 g 之外没有其他东西请求 c,而 g 也请求 f,现在不清楚 c 应该在 f、e 或 d 之前/之后执行。<为 c 设置的唯一规则是它必须在 b 之后和 g 之前执行。
在这种情况下,pytest 不知道 c 应该放在哪里,所以应该假定它可以在 g 和 b 之间的任何位置。
这不一定是坏事,但需要牢记。如果它们的执行顺序可能影响测试目标行为,或者可能影响测试结果,那么应该以允许 pytest 线性化/“扁平化”该顺序的方式明确定义顺序。
Autouse fixture 在其范围内首先执行¶
Autouse fixture 被假定适用于所有可以引用它们的测试,因此它们在该范围内的其他 fixture 之前执行。被 autouse fixture 请求的 fixture 对于实际 autouse fixture 所应用的测试来说,实际上也变成了 autouse fixture。
因此,如果 fixture a 是 autouse 的,而 fixture b 不是,但 fixture a 请求了 fixture b,那么 fixture b 实际上也将是 autouse fixture,但仅限于 a 适用的测试。
在上一个例子中,如果 d 没有请求 c,图表就变得不清楚了。但是如果 c 是 autouse 的,那么 b 和 a 实际上也会变成 autouse 的,因为 c 依赖于它们。因此,它们都会在该范围内的非 autouse fixture 之上。
因此,如果测试文件看起来像这样
from __future__ import annotations
import 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 实际上成为 autouse fixture。
但是要小心使用 autouse,因为 autouse fixture 会自动为所有可以访问它的测试执行,即使它们没有请求它。例如,考虑这个文件
from __future__ import annotations
import 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 中没有任何东西请求 c1,但它仍然为其中的测试执行
但仅仅因为一个 autouse fixture 请求了一个非 autouse fixture,并不意味着该非 autouse fixture 变成了适用于所有上下文的 autouse fixture。它只在实际的 autouse fixture(请求非 autouse fixture 的那个)可以应用的上下文中实际上变成一个 autouse fixture。
例如,看看这个测试文件
from __future__ import annotations
import 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 实际上使 c2 成为一个 autouse fixture,这就是为什么 c2 和 c3 为这两个测试执行,尽管没有被请求,以及为什么 c2 和 c3 在 test_req 的 c1 之前执行。
如果这使得 c2 成为一个 *实际的* autouse fixture,那么 c2 也将为 TestClassWithoutAutouse 中的测试执行,因为如果它们愿意,它们可以引用 c2。但它不会,因为从 TestClassWithoutAutouse 测试的角度来看,c2 不是一个 autouse fixture,因为它们看不到 c3。