pytestプラグイン探索-hook開発
前言
公式のこの文章を参考にして、私はその中のいくつかの重点部分を翻訳して、そして関連するplugy部分の知識を広げました.pytestはplugyに基づいて構築されているので、plugyの公式ドキュメントを先に読むことを強くお勧めします.そうすれば理解しやすくなります.
本文
conftest.pyは、最も簡単なローカルpluginとしていくつかのhook関数を呼び出し、強化機能を行うことができます.
pytestフレームワーク全体は、次のように定義された良好なhooksを呼び出すことによって、構成、収集、実行、およびレポートを実現します.内蔵plugins:コード内部からの_pytestディレクトリのロード; 外部カード(サードパーティカード):setuptools entry pointsメカニズムで発見されたサードパーティカードモジュール; conftest.py形式のローカルプラグイン:ディレクトリの下の自動モジュール発見メカニズムをテストする;
原則として、各hookは、1つの
decorrator
plugyには、HookspecMarkerとHookimplMarkerの2つのdecorator helperクラスがあり、同じproject_を使用することでnameパラメータを初期化して対応する装飾器を得,その後,この装飾器で関数をhookspecとhookimplとマークすることができる.
hookspec
hook specification(hookspec)はvalidateの各hookimplに使用され、hookimplが正しく定義されることを保証する.
hookspec add_を通過hookspecs()メソッドのロードは、一般的にhookimplを登録する前にロードされます.
hookimpl
hook implementation(hookimpl)は、適切にマークされたコールバック関数である.hookimplsはregister()メソッドでロードされます.
注意:hookspecsがプロジェクトで絶えず進化することを保証するために、hookspecのパラメータはhookimplsに対してオプションであり、specで定義された数より少ないパラメータを定義することができます.
hookwrapper
hookimplには、この関数がhookwrapper関数であることを示すhookwrapperオプションもあります.hookwrapper関数は、@contextlibと同様に、通常のwrapper以外のhookimplsが実行する前後にいくつかの他のコードを実行することができる.contextmanager、hookwrapperは、ジェネレータ関数を実装するために、その本体に単一のyieldを含む必要があります.たとえば、次のようにします.
ジェネレータは、
注意:hookwrapperは結果を返すことはできません(すべてのジェネレータ関数と同じです).
hookimplの呼び出し順序
デフォルトでは、hookの呼び出し順序は登録時の順序LIFO(後進先出)に従い、hookimplではtryfirst、trylast*オプションでこの順序を調整できます.
たとえば、次のコードについて説明します.
実行順序は次のとおりです.Plugin 3のpytest_collection_modifyitemsは、hook warpperであるため、yieldポイントまで呼び出されます. Plugin 1のpytest_collection_modifyitemsは、 Plugin 2のpytest_collection_modifyitemsが呼び出され、 Plugin 3のpytest_collection_modifyitemsはyieldの後のコードを呼び出す.yieldはWrapper以外のresultを受信する.Wrapper関数はこのresultを変更するべきではありません.
当然ながら、
hook実行結果処理とfirstresultオプション
デフォルトでは、hookを呼び出すと、最下位のhookimpl関数がループ内で順番に実行され、空でない実行結果がリストに追加されます.例外として、hookspecにはfirstresultオプションがあります.このオプションをtrueと指定すると、空でない結果を最初に返すhookimplが実行された後、直接戻ります.後続のhookimplは実行されません.後述の例を参照してください.
注意:hookwrapperは正常に実行されています
hookの呼び出し
各plugy.PluginManagerにはhookプロパティがあり、このプロパティのcall関数を呼び出すことでhookを呼び出すことができます.呼び出すときはキーワードパラメータ構文を使用して呼び出さなければなりません.
次のfirstresultとhook呼び出しの例を見てください.
結果は次のとおりです.
自動登録プラグイン
従来のregister()メソッド登録プラグインに加えて、plugyはload_を提供しています.setuptools_entrypoints()メソッドでは、setuptools entry pointsでプラグインを自動的に登録できます.
公式のこの文章を参考にして、私はその中のいくつかの重点部分を翻訳して、そして関連するplugy部分の知識を広げました.pytestはplugyに基づいて構築されているので、plugyの公式ドキュメントを先に読むことを強くお勧めします.そうすれば理解しやすくなります.
本文
conftest.pyは、最も簡単なローカルpluginとしていくつかのhook関数を呼び出し、強化機能を行うことができます.
pytestフレームワーク全体は、次のように定義された良好なhooksを呼び出すことによって、構成、収集、実行、およびレポートを実現します.
原則として、各hookは、1つの
1:N
python関数呼び出しであり、ここでのN
は、1つの与えられたhookに対するすべての登録呼び出し数である.すべてのhook関数はpytest_を使用します.xxxの命名規則は、検索を容易にし、他の関数と区別するために使用されます.decorrator
plugyには、HookspecMarkerとHookimplMarkerの2つのdecorator helperクラスがあり、同じproject_を使用することでnameパラメータを初期化して対応する装飾器を得,その後,この装飾器で関数をhookspecとhookimplとマークすることができる.
hookspec
hook specification(hookspec)はvalidateの各hookimplに使用され、hookimplが正しく定義されることを保証する.
hookspec add_を通過hookspecs()メソッドのロードは、一般的にhookimplを登録する前にロードされます.
hookimpl
hook implementation(hookimpl)は、適切にマークされたコールバック関数である.hookimplsはregister()メソッドでロードされます.
注意:hookspecsがプロジェクトで絶えず進化することを保証するために、hookspecのパラメータはhookimplsに対してオプションであり、specで定義された数より少ないパラメータを定義することができます.
hookwrapper
hookimplには、この関数がhookwrapper関数であることを示すhookwrapperオプションもあります.hookwrapper関数は、@contextlibと同様に、通常のwrapper以外のhookimplsが実行する前後にいくつかの他のコードを実行することができる.contextmanager、hookwrapperは、ジェネレータ関数を実装するために、その本体に単一のyieldを含む必要があります.たとえば、次のようにします.
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
ジェネレータは、
pluggy.callers._Result
式で指定してyield
またはforce_result()
メソッドで書き換えたり、最終結果を取得したりできるget_result()
オブジェクトを送信します.注意:hookwrapperは結果を返すことはできません(すべてのジェネレータ関数と同じです).
hookimplの呼び出し順序
デフォルトでは、hookの呼び出し順序は登録時の順序LIFO(後進先出)に従い、hookimplではtryfirst、trylast*オプションでこの順序を調整できます.
たとえば、次のコードについて説明します.
# 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
実行順序は次のとおりです.
tryfirst=True
パラメータがあるため呼び出されます.trylast=True
パラメータがあるため(ただしこのパラメータがなくてもtryfirstタグのpluginの後ろに並ぶ).当然ながら、
tryfirst
とtrylast
とhookwrapper=True
を同時に混用してもよいが、この場合hookwrapper間の呼び出し順序に影響を与える.hook実行結果処理とfirstresultオプション
デフォルトでは、hookを呼び出すと、最下位のhookimpl関数がループ内で順番に実行され、空でない実行結果がリストに追加されます.例外として、hookspecにはfirstresultオプションがあります.このオプションをtrueと指定すると、空でない結果を最初に返すhookimplが実行された後、直接戻ります.後続のhookimplは実行されません.後述の例を参照してください.
注意:hookwrapperは正常に実行されています
hookの呼び出し
各plugy.PluginManagerにはhookプロパティがあり、このプロパティのcall関数を呼び出すことでhookを呼び出すことができます.呼び出すときはキーワードパラメータ構文を使用して呼び出さなければなりません.
次のfirstresultとhook呼び出しの例を見てください.
from pluggy import PluginManager, HookimplMarker, HookspecMarker
hookspec = HookspecMarker("myproject")
hookimpl = HookimplMarker("myproject")
class MySpec1(object):
@hookspec
def myhook(self, arg1, arg2):
pass
class MySpec2(object):
# firstresult True
@hookspec(firstresult=True)
def myhook(self, arg1, arg2):
pass
class Plugin1(object):
@hookimpl
def myhook(self, arg1, arg2):
"""Default implementation.
"""
return 1
class Plugin2(object):
# hookimpl hookspec , arg1
@hookimpl
def myhook(self, arg1):
"""Default implementation.
"""
return 2
class Plugin3(object):
# , hookspec
@hookimpl
def myhook(self):
"""Default implementation.
"""
return 3
pm1 = PluginManager("myproject")
pm2 = PluginManager("myproject")
pm1.add_hookspecs(MySpec1)
pm2.add_hookspecs(MySpec2)
pm1.register(Plugin1())
pm1.register(Plugin2())
pm1.register(Plugin3())
pm2.register(Plugin1())
pm2.register(Plugin2())
pm2.register(Plugin3())
# hook
print(pm1.hook.myhook(arg1=None, arg2=None))
print(pm2.hook.myhook(arg1=None, arg2=None))
結果は次のとおりです.
[3, 2, 1]
3
pm2
のhookspecにfirstresultパラメータがあるため、3
この非空結果が得られたときにそのまま返されることがわかる.自動登録プラグイン
従来のregister()メソッド登録プラグインに加えて、plugyはload_を提供しています.setuptools_entrypoints()メソッドでは、setuptools entry pointsでプラグインを自動的に登録できます.