Pytest精通指南(05)Fixture源码拆解和自定义前后夹具

46 篇文章 1 订阅
28 篇文章 1 订阅


请添加图片描述

前言

固件(Fixture)pytest的核心功能,也是其亮点功能;

它提供了一种灵活的方式来定义和共享测试前的设置和测试后的清理逻辑。

与内置的setup/teardown方法相比,fixture提供了更大的灵活性和可重用性。

前文已经讲解了固件(Fixture)是什么以及它的核心概念;

也明白了固件(Fixture)会影响对应作用域下的测试用例函数;

本文将不再赘述这些,而继续深入固件Fixture高级用法;

Fixture 的特点

  1. 命名灵活Fixture可以通过函数名被引用,这意味着可以给它们起任何喜欢的名字。
  2. 可重用性Fixture可以在多个测试函数之间共享,提高了代码的复用性。
  3. 作用域灵活Fixture可以定义不同的作用域,如函数级、类级、模块级或会话级,以满足不同的测试需求。
  4. 依赖注入:一个fixture可以依赖于其他fixture,这样可以组织复杂的测试环境。
  5. 参数化Fixture可以接受参数,使得它可以更加灵活地适应不同的测试场景。
  6. 自动应用:通过装饰器或conftest.py配置文件,fixture可以自动应用到相关的测试函数上。

Fixture 的工作原理

  1. 定义Fixture:使用@pytest.fixture装饰器来定义fixture函数。
  2. 注册FixtureFixture函数可以通过直接定义在测试模块中,或定义在conftest.py文件中来注册。
  3. 引用Fixture:在测试函数中,通过参数形式引用fixturepytest会自动处理其执行顺序和依赖关系。
  4. 执行顺序:如果多个fixture之间存在依赖关系,pytest会确保它们按照正确的顺序执行。
  5. 清理工作:无论测试是否成功,pytest都会确保所有fixture的清理工作得到执行。
  6. 作用域管理:根据fixture的定义,pytest会管理其作用域,确保资源在正确的时机被分配和释放。

Fixture 的使用场景

  • 数据库连接的建立和关闭。
  • 临时文件或目录的创建和删除。
  • 网络请求的模拟和验证。
  • 环境变量的设置和恢复。
  • 复杂测试环境的搭建和拆解。

Fixture 的源码拆解

fixture是一个装饰器工厂函数,当使用@pytest.fixture装饰一个函数时,@pytest.fixture 装饰器会修改被装饰的函数的行为,使得该函数成为一个特殊的 fixture 函数。

pytest 的测试会话期间,这些 fixture 函数会按照它们的依赖关系被调用,并且它们的返回值可以被注入到其他测试函数或其他的 fixture 函数中。

fixture函数源码如下,我对其功能描述和各个参数进行了粗略翻译

def fixture(
        fixture_function: Optional[FixtureFunction] = None,
        *,
        scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = "function",
        params: Optional[Iterable[object]] = None,
        autouse: bool = False,
        ids: Optional[
            Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
        ] = None,
        name: Optional[str] = None,
) -> Union[FixtureFunctionMarker, FixtureFunction]:
    """
    功能描述翻译:
    fixture装饰器可以用来标记一个夹具工厂函数。
    这个装饰器可以带参数,也可以不带参数来定义一个夹具函数。
    夹具函数的名称可以后续被引用,以便在运行测试之前调用它:
    测试模块或类可以使用pytest.mark.usefixtures(fixturename)标记。
    测试函数可以直接使用夹具名称作为输入参数,在这种情况下,从夹具函数返回的夹具实例将被注入。
    夹具可以使用return或yield语句向测试函数提供它们的值。
    使用yield时,yield语句后的代码块将作为拆卸代码执行,不管测试结果如何,都必须恰好yield一次。

    Decorator to mark a fixture factory function.

    This decorator can be used, with or without parameters, to define a
    fixture function.

    The name of the fixture function can later be referenced to cause its
    invocation ahead of running tests: test modules or classes can use the
    ``pytest.mark.usefixtures(fixturename)`` marker.

    Test functions can directly use fixture names as input arguments in which
    case the fixture instance returned from the fixture function will be
    injected.

    Fixtures can provide their values to test functions using ``return`` or
    ``yield`` statements. When using ``yield`` the code block after the
    ``yield`` statement is executed as teardown code regardless of the test
    outcome, and must yield exactly once.

    :param scope:
        定义夹具的作用域。可以是"function"(默认)、"class"、"module"、"package"或"session"中的一个。
        也可以是一个接受(fixture_name, config)作为参数的可调用对象,返回上述作用域之一的字符串。
        作用域决定了夹具的生命周期。
        The scope for which this fixture is shared; one of ``"function"``
        (default), ``"class"``, ``"module"``, ``"package"`` or ``"session"``.

        This parameter may also be a callable which receives ``(fixture_name, config)``
        as parameters, and must return a ``str`` with one of the values mentioned above.

        See :ref:`dynamic scope` in the docs for more information.

    :param params:
        可选,一个参数列表,用于参数化夹具。
        这将导致夹具函数和使用它的测试被多次调用,每次使用不同的参数。
        An optional list of parameters which will cause multiple invocations
        of the fixture function and all of the tests using it. The current
        parameter is available in ``request.param``.

    :param autouse:
        如果为True,对于能看到该夹具的所有测试,夹具将自动使用。
        如果为False(默认),需要显式引用夹具才能激活它
        If True, the fixture func is activated for all tests that can see it.
        If False (the default), an explicit reference is needed to activate
        the fixture.

    :param ids:
        与params相对应的ID序列,这些ID将成为测试ID的一部分。如果没有提供ID,将自动从参数生成。
        Sequence of ids each corresponding to the params so that they are
        part of the test id. If no ids are provided they will be generated
        automatically from the params.

    :param name:
    	夹具的名称。默认情况下,使用装饰函数的名称。
    	如果在定义夹具的同一个模块中使用夹具,夹具函数的名称会被请求夹具的函数参数名遮蔽;
    	解决这个问题的一种方法是将装饰的函数命名为fixture_<fixturename>,
    	然后使用@pytest.fixture(name='<fixturename>')。
        The name of the fixture. This defaults to the name of the decorated
        function. If a fixture is used in the same module in which it is
        defined, the function name of the fixture will be shadowed by the
        function arg that requests the fixture; one way to resolve this is to
        name the decorated function ``fixture_<fixturename>`` and then use
        ``@pytest.fixture(name='<fixturename>')``.
    """
    fixture_marker = FixtureFunctionMarker(
        scope=scope,
        params=tuple(params) if params is not None else None,
        autouse=autouse,
        ids=None if ids is None else ids if callable(ids) else tuple(ids),
        name=name,
        _ispytest=True,
    )

    # Direct decoration.
    if fixture_function:
        return fixture_marker(fixture_function)

    return fixture_marker

Fixture 的高级用法

使用yield关键字实现自定义前后置:

  • yield关键字在fixture函数中的使用,允许我们定义在测试执行前后需要执行的代码。
  • fixture函数被调用时,yield之前的代码会首先执行(设置逻辑),然后控制权返回给调用者执行测试。
  • 测试完成后,yield之后的代码会执行(清理逻辑)。

错误处理与前后置分离:

  • 如果fixture的前置逻辑发生错误,pytest将不会执行该fixture的后续清理逻辑。
  • 为了确保即使在设置失败时也能执行清理操作,我们可以将前后置逻辑分离到两个独立的fixture中。
  • 这样,即使设置fixture失败,清理fixture仍然可以被调用。

作用域与共享:

  • fixture可以定义不同的作用域,如函数级、类级、模块级或会话级。
  • 这使得fixture可以在不同的测试级别之间共享资源,提高了代码的复用性和测试效率。

依赖注入与灵活配置:

  • 除了作为设置和清理逻辑外,fixture还可以作为参数传递给测试函数,实现了依赖注入的概念。
  • 这使得我们可以更加灵活地配置和管理测试环境。
示例1:使用yield实现前后置逻辑
import pytest

# 使用yield实现前置和后置逻辑
@pytest.fixture
def setup_teardown_fixture():
    print("前置逻辑:设置环境")
    yield  # 控制权返回给调用者执行测试
    print("后置逻辑:清理环境")

# 测试函数,依赖上面的fixture
def test_example(setup_teardown_fixture):
    assert True, "测试通过"

请添加图片描述

示例2:错误处理与前后置分离

pytest中,request是一个特殊的对象,它包含了当前fixture函数调用的上下文信息。

pytest执行一个fixture函数时,它会传递一个request对象作为参数。

这个对象提供了一些方法和属性,帮助在fixture函数中进行操作。

request对象中的其他常用属性(了解即可)

  • request.function:当前正在执行的测试函数。
  • request.node:当前正在执行的测试项(可能是函数、类或模块)。
  • request.cls:如果当前正在执行的是一个类中的方法,这个属性将是该类;否则为None
  • request.session:整个测试会话的引用。
  • request.config:当前配置的引用,允许访问命令行选项等。
import pytest


# 前置逻辑fixture
@pytest.fixture
def setup_fixture():
    print("\n前置逻辑:设置环境")


# 后置逻辑fixture
@pytest.fixture
def teardown_fixture(request):
    def fin():
        print("\n后置逻辑:清理环境")

    # addfinalizer方法用于注册一个清理函数(finalizer),
    # 该函数会在fixture函数执行完毕后(无论成功还是失败)被调用。
    request.addfinalizer(fin)


# 测试函数,依赖上面的两个fixture
def test_example(setup_fixture, teardown_fixture):
    assert True, "测试通过"

请添加图片描述

示例3:作用域与共享
import pytest

# 类级作用域的fixture
@pytest.fixture(scope="class")
def class_scope_fixture():
    print("类级前置逻辑:设置环境")
    yield
    print("类级后置逻辑:清理环境")

# 函数级作用域的 fixture
@pytest.fixture
def function_scope_fixture():
    print("函数级前置逻辑:设置环境")
    yield
    print("函数级后置逻辑:清理环境")

# 测试类,依赖类级作用域的fixture
class TestClass:
    def test_method1(self, class_scope_fixture):
        assert True, "测试通过"
    
    def test_method2(self, function_scope_fixture):
        assert True, "另一个测试通过"

# 普通测试函数,依赖函数级作用域的fixture
def test_example(function_scope_fixture):
    assert True, "另一个测试通过"

请添加图片描述

示例4:依赖注入
import pytest


# 灵活的fixture配置
@pytest.fixture(params=[1, 2, 3])
def flexible_fixture(request):
    value = request.param
    print(f"灵活的fixture参数值:{value}")
    return value


# 测试函数,接收来自fixture的参数
def test_with_params(flexible_fixture):
    assert flexible_fixture > 0, f"参数值 {flexible_fixture} 必须大于0"

请添加图片描述

示例5:使用多个fixture
import pytest

# 定义第一个fixture函数,用于设置数据库连接
@pytest.fixture(scope="module")
def database_connection():
    print("建立数据库连接")
    # 这里可以是实际建立数据库连接的代码
    yield
    print("关闭数据库连接")
    # 这里可以是实际关闭数据库连接的代码

# 定义第二个fixture函数,用于登录用户
@pytest.fixture(scope="function")
def logged_in_user(database_connection):
    print("用户登录")
    # 这里可以是实际登录用户的代码,依赖于数据库连接
    yield
    print("用户登出")
    # 这里可以是实际登出用户的代码

# 定义第三个fixture函数,用于模拟外部API响应
@pytest.fixture
def external_api_response():
    print("模拟外部API响应")
    # 这里可以是实际模拟外部API响应的代码
    response = {"status": "success", "data": {"message": "Hello, World!"}}
    yield response
    print("外部API响应模拟结束")

# 使用@pytest.mark.usefixtures装饰器,指定自动应用于测试的fixture函数
@pytest.mark.usefixtures("database_connection", "logged_in_user", "external_api_response")
def test_example():
    # 在这个测试函数中,我们可以直接使用上面定义的三个fixture函数的功能
    assert True, "测试通过"

# 另一个测试函数,也会自动应用相同的fixture函数
@pytest.mark.usefixtures("database_connection", "logged_in_user", "external_api_response")
def test_another_example():
    assert True, "另一个测试通过"

请添加图片描述

  • 27
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

需要休息的KK.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值