編寫鈎子(Hooks)函數
鈎子函數驗證和執行
Pytest會調用任意給定規格並注冊了的插件的鈎子方法。讓我們看一下一個函數的典型鈎子函數
pytest_collection_modifyitems(session,config,items),Pytest在收集完所有測試用例后調用該鈎子方法。
當我們在自己的插件中實現一個pytest_collection_modifyitems函數時,Pytest將在注冊期間驗證你是否使用了與規范匹配的參數名稱,如果不符合規范,則廢棄掉該方法。
讓我們看一下實現該插件的方法:
def pytest_collection_modifyitems(config,items):
# 在收集完測試用例后執行
# 你可以修改items用例列表
...
這里,Pytest將傳入config(pytest配置對象)和items(收集的測試用例列表),但不會傳入session參數,因為我們沒有在函數簽名中列出它。這種動態的改動參數允許Pytest進行一些“未來兼容”:我們可以引入新的鈎子函數命名參數而不破壞現有鈎子函數實現的簽名,這是Pytest插件的一般可以長期兼容的原因之一。
請注意,除了pytest_runtest_*這種測試運行期間的鈎子方法,其他鈎子方法不允許拋出異常,不然會破壞Pytest的運行流程。
firstresult: 遇到第一個有效(非None)結果返回
通常Pytest鈎子函數的調用,都會產生一個包含所有所調用鈎子方法的有效結果組成的列表。
一些鈎子函數規格使用firstresult=True選項,以便鈎子函數調用,直到多個個注冊鈎子函數中的第一個返回有效結果,然后將其作為整個鈎子函數調用的結果。這種情況下,其余的鈎子函數不會再調用。
hookwrapper:在其他鈎子函數周圍執行
版本2.7中的新函數。
Pytest插件可以實現鈎子函數裝飾器,它包裝其他鈎子函數實現的執行。鈎子函數裝飾器是一個生成器函數,它只生成一次。當Pytest調用鈎子函數時,它首先執行鈎子函數裝飾器並傳遞與常規鈎子函數相同的參數。
在鈎子函數裝飾器的yield處,Pytest將執行下一個鈎子函數實現,並以Result對象的形式,封裝結果或異常信息的實例的形式將其結果返回到yield處。因此,yield處通常本身不會拋出異常(除非存在錯誤)。
以下是鈎子函數裝飾器的示例:
import pytest
@pytest.hookimpl(hookwrapper=True)
def pytest_pyfunc_call(pyfuncitem):
do_something_before_next_hook_executes()
outcome = yield
# outcome的退出信息(outcome.excinfo)可能是None或者(cls,val,tb)組成的元祖
res = outcome.get_result() # 如果outcome出錯會拋出異常
post_process_result(res)
outcome.force_result(new_res) # 覆蓋插件系統的返回值
請注意,鈎子函數裝飾器本身不返回結果,它們只是圍繞實際的鈎子函數實現執行跟蹤或其他額外作用。如果底層鈎子函數的結果是一個可變對象,這可能會修改該結果,因此最好避免對動態結果這樣使用。
有關更多信息,請參閱:插件文檔。
鈎子(Hooks)函數排序/調用示例
對於任何給定的鈎子函數規格,可能存在多個實現,因此我們通常將鈎子函數執行視為1:N的函數調用,其中N是已注冊函數的數量。有一些方法可以影響鈎子函數實現是在其他之前還是之后,即在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被調用直到注入點,因為它是一個鈎子函數裝飾器。
- 調用Plugin1的pytest_collection_modifyitems是因為它標有
tryfirst=True。 - 調用Plugin2的pytest_collection_modifyitems因為它被標記`trylast=True(但即使沒有這個標記,它也會在Plugin1之后出現)。
- 插件3的pytest_collection_modifyitems然后在注入點之后執行代碼。yield接收一個
Result實例,該實例封裝了調用非裝飾器的結果。包裝不得修改結果。
這是可能的使用tryfirst,並trylast結合還hookwrapper=True處於這種情況下,它會影響彼此之間hookwrappers的排序。
聲明新的鈎子函數
插件和conftest.py文件可以聲明新鈎子函數,然后可以由其他插件實現,以便改變行為或與新插件交互:
在插件注冊時調用,允許通過調用添加新的鈎子函數。pluginmanager.add_Hookspecs(module_or_class,prefix)
參數: pluginmanager(_pytest.config.PytestPluginManager) - Pytest插件管理器
注意:
這個鈎子函數與之不相容hookwrapper=True。
鈎子函數通常被聲明為do-nothing函數,它們只包含描述何時調用鈎子函數以及期望返回值的文檔。
有關示例,請參閱:xdist插件。
使用第三方插件的鈎子函數
由於標准的驗證機制,方法可能有點棘手:如果你依賴未安裝的插件,驗證將失敗並且錯誤消息對你的用戶沒有多大意義。
一種方法是將鈎子函數實現推遲到新的插件,而不是直接在插件模塊中聲明鈎子函數,例如:
# 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())
這具有額外的好處,允許你根據安裝的插件有條件地安裝鈎子。
