洛克王国 发表于 2024-1-29 00:22:23

Pytest 源码解读 [1] - [pluggy] 核心设计理念浅读

背景:

Pytest 是一个功能强大的 Python 测试框架,它使用了一个名为 "pluggy" 的插件系统来扩展其功能。在 Pytest 的源码中,pluggy 模块负责实现插件管理和扩展机制。
核心类介绍:

PluginManager 类:PluginManager 是 pluggy 模块提供的一个类,用于管理插件的加载、注册和调用。它负责协调插件之间的交互,并控制钩子函数的执行顺序。
HookspecMarker 类:HookspecMarker 是 pluggy 模块提供的一个装饰器类,用于定义钩子函数规范。通过使用 HookspecMarker 装饰器,我们可以标识出一个函数作为钩子函数规范,以便在后续的插件中进行实现。
HookimplMarker 类:HookimplMarker 是 pluggy 模块提供的一个装饰器类,用于定义钩子函数的具体实现。通过使用 HookimplMarker 装饰器,我们可以将一个函数标识为钩子函数的具体实现,并将其注册到插件管理器中。
核心逻辑代码:

pm = PluginManager("pluggy_demo_1")
pm.add_hookspecs(HookSpec)
pm.register(HookImpl1())
pm.register(HookImpl2())
print(pm.hook.calculate(a=1, b=2))首先,我们创建一个插件管理器 pm,并指定项目名称为 "pluggy_demo_1"。然后,我们使用 add_hookspecs() 方法将钩子函数规范 HookSpec 添加到插件管理器中。这样,插件管理器就知道了我们定义的钩子函数规范。
接下来,我们注册两个插件 HookImpl1() 和 HookImpl2() 到插件管理器中,通过调用 register() 方法。这样,插件管理器就知道了需要执行这两个插件的钩子函数。
最后,我们通过 pm.hook.calculate() 调用了钩子函数 calculate。pm.hook 是插件管理器提供的一个特殊属性,它允许我们访问所有已注册的钩子函数。通过调用 pm.hook.calculate(),插件管理器会触发所有已注册的 calculate 钩子函数,并将参数 a=1 和 b=2 传递给它们。
在插件中,我们可以根据具体需求实现 calculate 钩子函数的逻辑。可以有多个插件实现了该钩子函数,每个插件的实现可以根据自己的逻辑进行计算,并返回计算结果。
总结起来,这段代码中的 pm.hook.calculate 调用了已注册的 calculate 钩子函数,并触发了相应的插件逻辑。pm.hook 是插件管理器提供的特殊属性,用于访问已注册的钩子函数。通过调用该属性上的钩子函数,可以实现插件的扩展和自定义行为。
常用的 Pytest 钩子函数:


[*]pytest_configure(config): 在 Pytest 运行之前,用于配置和初始化测试运行环境。
[*]pytest_collection_modifyitems(config, items): 在测试收集过程中修改测试项(test items)的钩子函数。
[*]pytest_runtest_protocol(item, nextitem): 在执行单个测试项之前和之后,以及在测试项之间进行干预的钩子函数。
[*]pytest_pyfunc_call(pyfuncitem): 在执行测试函数之前和之后进行干预的钩子函数。
[*]pytest_terminal_summary(terminalreporter): 在测试运行完成后,用于生成测试结果摘要的钩子函数。
自定义钩子函数步骤

步骤1:定义钩子函数接口(Hook specification)

[*]创建一个 Python 模块,例如 myhooks.py。
[*]在该模块中,使用 @pytest.hookspec 装饰器来标记你的钩子函数接口。例如:
# myhooks.py
import pytest

@pytest.hookspec
def my_custom_hook(arg1, arg2):
    """Documentation for the custom hook."""
    pass在这个例子中,my_custom_hook 是你的自定义钩子函数接口。你可以根据需要定义参数和返回值,以及提供相应的文档说明。
步骤2:实现钩子函数(Hook implementation)

[*]创建另一个 Python 模块,例如 myplugin.py。
[*]在该模块中,使用 @pytest.hookimpl 装饰器来标记你的钩子函数实现。例如:
# myplugin.py
import pytest

@pytest.hookimpl
def my_custom_hook(arg1, arg2):
    """Implementation of the custom hook."""
    # 执行自定义的逻辑
    print(f"Running custom hook with arguments: {arg1}, {arg2}")
    return 42在这个例子中,my_custom_hook 是你的自定义钩子函数实现。你可以在函数体内编写你的自定义逻辑,并返回相应的结果。
步骤3:注册自定义插件

[*]创建一个用于注册插件的 Python 模块,例如 conftest.py。这个文件通常放置在测试目录的根目录或者 tests 目录下。
[*]在 conftest.py 中,使用 pytest_configure 钩子函数来注册你的插件。例如:
# conftest.py
def pytest_configure(config):
    config.pluginmanager.register(myplugin)在这个例子中,myplugin 是你在 myplugin.py 中定义的插件。通过调用 register 方法,你可以将插件注册到 Pytest 的插件管理器中。
步骤4:使用自定义钩子函数

[*]在测试代码中,你可以通过使用 pytestconfig 对象来访问自定义的钩子函数。
[*]调用自定义钩子函数,并传入相应的参数。例如:
# test_myhooks.py
def test_with_custom_hook(pytestconfig):
    hook_result = pytestconfig.hook.my_custom_hook(arg1=1, arg2=2)
    # 处理钩子函数返回的结果
    assert hook_result == 42在这个例子中,我们通过 pytestconfig.hook 访问了自定义的钩子函数,并传入了参数 arg1 和 arg2。你可以根据你的钩子函数逻辑来处理返回的结果。
需要注意的是,为了让 Pytest 能够识别和加载自定义的钩子函数,确保将 myhooks.py、myplugin.py 和 conftest.py 放置在测试的搜索路径下,例如项目根目录或者 tests 目录下。
  
  
  
  

来源:https://www.cnblogs.com/beyond-tester/p/17993547
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: Pytest 源码解读 [1] - [pluggy] 核心设计理念浅读