关于 fixtures

pytest fixture 被设计成显式的、模块化的和可扩展的。

fixtures 是什么

在测试中,fixture 为测试提供一个定义的、可靠的和一致的上下文。这可能包括环境(例如配置了已知参数的数据库)或内容(例如数据集)。

fixture定义了组成测试的 arrange 阶段的步骤和数据(参见 测试的解剖)。

测试函数通过参数访问由 fixture 设置的服务、状态或其他操运算环境。对于测试函数使用的每个 fixture,在测试函数的定义中通常都有一个参数(以 fixture 命名)。

可以通过用 @pytest.fixture 装饰器来告诉 pytest 特定的函数是 fixture。下面是简单的例子,说明 pytest 中的 fixture 可能是什么样子的:

import pytest


class Fruit:
    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        return self.name == other.name


@pytest.fixture
def my_fruit():
    return Fruit("apple")


@pytest.fixture
def fruit_basket(my_fruit):
    return [Fruit("banana"), my_fruit]


def test_my_fruit_in_basket(my_fruit, fruit_basket):
    assert my_fruit in fruit_basket

测试也不必局限于单一的 fixture。它们可以依赖于任意数量的 fixture,fixture 也可以使用其他 fixture。这是 pytest 的 fixture 系统真正的亮点。

对 xUnit 风格的 setup/teardown 函数的改进

pytest fixture 在经典的 xUnit 风格的 setup/teardown 函数上提供了显著的改进:

  • fixture 有显式名称,通过从测试函数、模块、类或整个项目中声明它们的使用来激活。

  • fixture 以模块化的方式实现,因为每个 fixture 名称触发一个 fixture 函数,该函数本身可以使用其他 fixture。

  • fixture 管理从简单的单元扩展到复杂的功能测试,允许根据配置和组件选项对夹具和测试进行参数化,或者跨函数、类、模块或整个测试会话范围重用 fixture。

  • 无论使用了多少个 fixture,都可以轻松安全地管理 teardown 逻辑,而不需要手工小心地处理错误或微管理添加清理步骤的顺序。

此外,pytest 继续支持 How to implement xunit-style set-up。你可以混合这两种风格,根据你的喜好从经典风格逐渐过渡到新风格。你也可以从现有的 unittest.TestCase stylenose based 项目开始。

Fixture 错误

pytest 尽最大努力将给定测试的所有 fixture 按线性顺序排列,以便它可以看到哪个 fixture 发生在第一个、第二个、第三个,等等。但是,如果早期的 fixture 有问题并引发异常,pytest 将停止执行该测试的 fixture,并将该测试标记为有错误。

当一个测试被标记为有错误时,并不意味着测试失败。这只是意味着测试甚至无法尝试,因为它所依赖的一个东西出了问题。

这就是为什么在给定的测试中尽可能地减少不必要的依赖是一个好主意。这样一来,不相关的问题就不会让我们对什么可能有问题或什么可能没有问题有不完整的认识。

这里有一个简单的例子来帮助解释:

import pytest


@pytest.fixture
def order():
    return []


@pytest.fixture
def append_first(order):
    order.append(1)


@pytest.fixture
def append_second(order, append_first):
    order.extend([2])


@pytest.fixture(autouse=True)
def append_third(order, append_second):
    order += [3]


def test_order(order):
    assert order == [1, 2, 3]

如果 order.append(1) 由于某种原因出现错误并引发异常,我们将无法知道 order.extend([2])order += [3] 是否也有问题。在 append_first 抛出异常之后,pytest 将不再为 test_order 运行任何 fixture,它甚至不会尝试运行 test_order 本身。唯一会运行的是 orderappend_first

共享测试数据

如果您想让来自文件的测试数据对您的测试可用,一个好方法是将这些数据加载到 fixture 中供您的测试使用。这利用了 pytest 的自动缓存机制。

另一个好方法是在 tests 文件夹中添加数据文件。还有一些社区插件可以帮助管理这方面的测试,例如 pytest-datadirpytest-datafiles

关于 fixture 清理的说明

pytest does not do any special processing for SIGTERM and SIGQUIT signals (SIGINT is handled naturally by the Python runtime via KeyboardInterrupt), so fixtures that manage external resources which are important to be cleared when the Python process is terminated (by those signals) might leak resources.

The reason pytest does not handle those signals to perform fixture cleanup is that signal handlers are global, and changing them might interfere with the code under execution.

If fixtures in your suite need special care regarding termination in those scenarios, see this comment in the issue tracker for a possible workaround.