Bootstrap

pytest8.x版本 中文使用文档-------22.解释:关于夹具 fixtures

目录

什么是fixtures

改进了xunit风格的设置/拆卸函数

Fixture错误

共享测试数据

关于fixture清理的注意事项


另请参阅

如何使用fixturesHow to use fixtures

另请参阅

fixtures 参考Fixtures reference

pytest 的 fixtures 被设计为明确、模块化和可扩展的。

什么是fixtures

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

fixtures定义了构成测试准备阶段(参见“测试的解剖”)的步骤和数据。在pytest中,它们是您定义的用于此目的的函数。它们也可以用于定义测试的执行阶段;这是设计更复杂测试的一种强大技术。

通过参数,测试函数可以访问由fixtures设置的服务、状态或其他运行环境。对于测试函数使用的每个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系统真正发光发热的地方。在这个例子中,fruit_basket fixture依赖于my_fruit fixture,而测试函数test_my_fruit_in_basket则依赖于这两个fixture。通过这种方式,pytest的fixture系统提供了一种强大而灵活的方式来组织和管理测试上下文。

改进了xunit风格的设置/拆卸函数

与xUnit风格的设置/拆卸函数相比,pytest的fixtures提供了显著的改进:

  1. 明确的命名和激活方式:fixtures具有明确的名称,并通过在测试函数、模块、类或整个项目中声明其使用来激活。这使得测试代码更加清晰和易于理解。

  2. 模块化实现:每个fixture名称都会触发一个fixture函数,而这些fixture函数本身也可以使用其他fixtures。这种模块化方式使得fixtures的复用和组合变得简单高效。

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

  4. 易于管理的拆卸逻辑:无论使用多少个fixtures,拆卸逻辑都可以轻松且安全地管理。您无需手动仔细处理错误或微调清理步骤的添加顺序。

  5. 对xUnit风格设置的支持:pytest继续支持如何实现xUnit风格的设置。您可以混合使用这两种风格,从经典风格逐步过渡到新风格,根据您的喜好进行。您还可以从现有的unittest.TestCase风格开始。

这些特性使得pytest的fixtures成为了一种强大而灵活的工具,用于组织和管理测试上下文,从而提高了测试的可维护性、可重用性和可扩展性。

Fixture错误

pytest会尽力将所有给定测试的fixtures组织成一个线性顺序,以便它能够确定哪个fixture首先执行,然后是第二个、第三个,依此类推。然而,如果前面的某个fixture出现问题并抛出异常,pytest将停止为该测试执行剩余的fixtures,并将该测试标记为出现错误。

当一个测试被标记为出现错误时,这并不意味着测试失败。它仅仅意味着由于测试所依赖的某个部分出现问题,因此甚至无法尝试执行该测试。

这就是为什么对于给定的测试,尽可能减少不必要的依赖是一个好主意的原因之一。这样,与测试无关的问题就不会导致我们无法全面了解测试可能存在的问题或没有问题的情况。

下面是一个简短的示例,用于帮助解释:

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运行任何更多的fixtures,甚至不会尝试运行test_order本身。唯一会运行的是orderappend_first

这种设计确保了测试环境的清晰和隔离,当测试依赖的某个部分失败时,测试不会继续执行并可能产生误导性的结果。它允许开发者快速定位问题所在,并只处理与当前测试直接相关的问题。

共享测试数据

如果你希望将文件中的测试数据提供给测试使用,一个很好的方法是通过在fixture中加载这些数据,以供测试使用。这样做可以利用pytest的自动缓存机制。

另一种好的做法是将数据文件添加到测试文件夹中。社区中还有可用的插件来帮助管理测试的这一方面,例如pytest-datadir 和 pytest-datafiles.。这些插件提供了更便捷的方式来处理测试数据文件的加载和路径管理。

关于fixture清理的注意事项

pytest不会对SIGTERM和SIGQUIT信号(SIGINT信号通过Python运行时的KeyboardInterrupt自然处理)进行任何特殊处理,因此,当Python进程通过这些信号被终止时,那些管理需要清除的外部资源的fixtures可能会导致资源泄漏。

pytest不处理这些信号以执行fixture清理的原因是,信号处理器是全局的,更改它们可能会干扰正在执行的代码。

如果你的套件中的fixtures在这些场景下终止时需要特别关注,请查看问题跟踪器中的这条评论 this comment,以获取可能的解决方案。这通常涉及到在测试开始前和结束时手动管理资源的分配和释放,或者使用支持更精细控制资源清理的第三方库。

;