Pytest官方教程-20-编写钩子(hook)方法
目录:
- 安装及入门
- 使用和调用方法
- 原有TestSuite使用方法
- 断言的编写和报告
- Pytest fixtures:清晰 模块化 易扩展
- 使用Marks标记测试用例
- Monkeypatching/对模块和环境进行Mock
- 使用tmp目录和文件
- 捕获stdout及stderr输出
- 捕获警告信息
- 模块及测试文件中集成doctest测试
- skip及xfail: 处理不能成功的测试用例
- Fixture方法及测试用例的参数化
- 缓存: 使用跨执行状态
- unittest.TestCase支持
- 运行Nose用例
- 经典xUnit风格的setup/teardown
- 安装和使用插件
- 插件编写
- 编写钩子(hook)方法
- 运行日志
- API参考
- 优质集成实践
- 片状测试
- Pytest导入机制及sys.path/PYTHONPATH
- 配置选项
- 示例及自定义技巧
- Bash自动补全设置
编写钩子(hook)方法
钩子(hook)方法验证和执行
pytest为任何给定的钩子(hook)规范调用已注册插件的钩子(hook)方法。让我们看一下钩子(hook)的典型钩子(hook)方法,pytest在收集完所有测试项目后调用。pytest_collection_modifyitems(session, config,items)
当我们pytest_collection_modifyitems
在插件中实现一个方法时,pytest将在注册期间验证你是否使用了与规范匹配的参数名称,如果没有则拯救。
让我们看一下可能的实现:
def pytest_collection_modifyitems(config, items):
# called after collection is completed
# you can modify the ``items`` list
...
这里,pytest
将传入config
(pytest配置对象)和items
(收集的测试项列表),但不会传入session
参数,因为我们没有在方法签名中列出它。这种动态的“修剪”参数允许pytest
“未来兼容”:我们可以引入新的钩子(hook)命名参数而不破坏现有钩子(hook)实现的签名。这是pytest插件的一般长期兼容性的原因之一。
请注意,除了pytest_runtest_*
不允许引发异常之外的钩子(hook)方法。这样做会打破pytest运行。
firstresult:首先停止非无结果
大多数对pytest
钩子(hook)的调用都会产生一个结果列表,其中包含被调用钩子(hook)方法的所有非None结果。
一些钩子(hook)规范使用该firstresult=True
选项,以便钩子(hook)调用仅执行,直到N个注册方法中的第一个返回非None结果,然后将其作为整个钩子(hook)调用的结果。在这种情况下,不会调用其余的钩子(hook)方法。
hookwrapper:在其他钩子(hook)周围执行
版本2.7中的新功能。
pytest插件可以实现钩子(hook)包装器,它包装其他钩子(hook)实现的执行。钩子(hook)包装器是一个生成器方法,它只产生一次。当pytest调用钩子(hook)时,它首先执行钩子(hook)包装器并传递与常规钩子(hook)相同的参数。
在钩子(hook)包装器的屈服点,pytest将执行下一个钩子(hook)实现,并以Result
封装结果或异常信息的实例的形式将其结果返回到屈服点。因此,屈服点本身通常不会引发异常(除非存在错误)。
以下是钩子(hook)包装器的示例定义:
import pytest
@pytest.hookimpl(hookwrapper=True)
def pytest_pyfunc_call(pyfuncitem):
do_something_before_next_hook_executes()
outcome = yield
# outcome.excinfo may be None or a (cls, val, tb) tuple
res = outcome.get_result() # will raise if outcome was exception
post_process_result(res)
outcome.force_result(new_res) # to override the return value to the plugin system
请注意,钩子(hook)包装器本身不返回结果,它们只是围绕实际的钩子(hook)实现执行跟踪或其他副作用。如果底层钩子(hook)的结果是一个可变对象,它们可能会修改该结果,但最好避免它。
有关更多信息,请参阅插件文档。
钩子(hook)方法排序/调用示例
对于任何给定的钩子(hook)规范,可能存在多个实现,因此我们通常将hook
执行视为 1:N
方法调用,其中N
是已注册方法的数量。有一些方法可以影响钩子(hook)实现是在其他人之前还是之后,即在N
-sized方法列表中的位置:
# Plugin 1
@pytest.hookimpl(tryfirst=True)
def pytest_collection_modifyitems(items):
# will execute as early as possible
...
# Plugin 2
@pytest.hookimpl(trylast=True)
def pytest_collection_modifyitems(items):
# will execute as late as possible
...
# Plugin 3
@pytest.hookimpl(hookwrapper=True)
def pytest_collection_modifyitems(items):
# will execute even before the tryfirst one above!
outcome = yield
# will execute after all non-hookwrappers executed
这是执行的顺序:
- Plugin3的pytest_collection_modifyitems被调用直到屈服点,因为它是一个钩子(hook)包装器。
- 调用Plugin1的pytest_collection_modifyitems是因为它标有
tryfirst=True
。 - 调用Plugin2的pytest_collection_modifyitems因为它被标记
trylast=True
(但即使没有这个标记,它也会在Plugin1之后出现)。 - 插件3的pytest_collection_modifyitems然后在屈服点之后执行代码。yield接收一个
Result
实例,该实例封装了调用非包装器的结果。包装不得修改结果。
这是可能的使用tryfirst
,并trylast
结合还 hookwrapper=True
处于这种情况下,它会影响彼此之间hookwrappers的排序。
声明新钩子(hook)
插件和conftest.py
文件可以声明新钩子(hook),然后可以由其他插件实现,以便改变行为或与新插件交互:
<dt>pytest_addhooks
(*pluginmanager *)[来源]
在插件注册时调用,允许通过调用添加新的挂钩 。pluginmanager.add_hookspecs(module_or_class, prefix)
参数: | pluginmanager(_pytest.config.PytestPluginManager) - pytest插件管理器
注意:
这个钩子(hook)与之不相容hookwrapper=True
。
钩子(hook)通常被声明为do-nothing方法,它们只包含描述何时调用钩子(hook)以及期望返回值的文档。
有关示例,请参阅xdist中的newhooks.py。
可选择使用第三方插件的钩子(hook)
由于标准的验证机制,如上所述使用插件中的新钩子(hook)可能有点棘手:如果你依赖未安装的插件,验证将失败并且错误消息对你的用户没有多大意义。
一种方法是将钩子(hook)实现推迟到新的插件,而不是直接在插件模块中声明钩子(hook)方法,例如:
# contents of myplugin.py
class DeferPlugin(object):
"""Simple plugin to defer pytest-xdist hook functions."""
def pytest_testnodedown(self, node, error):
"""standard xdist hook function.
"""
def pytest_configure(config):
if config.pluginmanager.hasplugin("xdist"):
config.pluginmanager.register(DeferPlugin())```