pytestプラグインプローブ

20664 ワード

引用する
Pytestは強力なテストフレームワークであり、プラグイン化されたテストプラットフォームでもあります.デルは、使用中にサードパーティ製プラグインをインストールしてフレームワーク機能を強化することがよくあります.では、自分でもプラグインを書いてもいいですか?
公式サイトの定義によれば、1つ以上のhook関数を含むモジュールはpytestプラグインと理解できる.(A plugin contains one or multiple hook functions writing-plugins)
pytestのconftest.pyファイルにはhook関数が書けるので、conftet.pyはローカルプラグインであり、もう1つはpipでインストールされた外部サードパーティプラグインです.
ローカルプラグイン
次は簡単なhook方法をconftest.pyに書いて、すぐにローカルプラグインを実現しました.
# conftest.py
def pytest_runtest_call(item):
    print('I am a simple pytest local plugin implementation')

外部プラグイン
次に、簡単な外部プラグインを書きます.
  • ディレクトリ構造外部プラグインパッケージは、以下のように一般的なpythonパッケージディレクトリフォーマットに従います.ここでは主にpluginでmodule.pyにpytestプラグインのhook関数を入れ、setup.pyにパッケージ情報を記述します.外部プラグインパッケージを簡素化するために、ディレクトリの下の他のファイルは空のままです.
    pytest_simple_plugin
    ├── setup.py
    ├── setup.cfg
    ├── README.rst
    ├── MANIFEST.in
    └── pytest_simple_plugin
        ├── __init__.py
        └── plugin_module.py
    
  • コードプラグインの主な機能は、最も簡単なhello worldプログラム
    """
    File: plugin_module.py
    Description: pytest plugin module file
    """
    from pytest import fixture
    
    
    @fixture(scope='module')
    def hello(request):
        name = request.config.getoption('--hello')
        print(f'Hello {name if name else "world"}')
    
    
    def pytest_addoption(parser):
        parser.addoption('--hello', action='store',
                         default=None, help='say hi')
    
    
    setup.pyキーを実現するentry_pointsにはプラグインがpytestプラグインとして登録され、プラグインのエントリモジュール
    """
    :Description: setup.py
    """
    import setuptools
    from os import path
    
    with open(path.join(path.dirname(__file__), 'README.rst')) as f:
        README = f.read()
    
    setuptools.setup(
        name='pytest-simple-plugin', #      ,  pip              
        packages=['pytest_simple_plugin'],
        version='0.0.4',
        description='Simple pytest plugin',
        long_description=README,
        url='',
        author='Li Liu',
        author_email="[email protected]",
        license='BSD License',
        install_requires=['pytest'],
        entry_points={ #          pytest  
            'pytest11': ['pytest_simple_plugin = pytest_simple_plugin.plugin_module']
        },
        classifiers=[
            "Framework :: Pytest",
        ],
        keywords=['testing', 'pytest'],
        python_requires='>=3',
    
    )
    
    
  • が示されている.
  • パッケージアップロード
  • パッケージpytestプラグインpython setup.py sdist
  • アップロードtwine upload --repository-url https://upload.pypi.org/legacy/ -u -p dist/*
  • テストプラグインダウンロードプラグインpip install pytest-simple-plugin後、pytest関数呼び出しプラグインのhello fixtureを書きます.次の
    """
    File: test_hello.py
    Description: test pytest plugin 'pytest-simple-plugin' hello fixure
    """
    def test_hello(hello):
        pass
    
    
    でpytestを実行します.ここで、-sパラメータは-capture=noの略記です.失敗したテスト例の情報だけを出力するのではなく、すべてのprint出力を印刷することができます.
    $ pytest -s test_hello.py --hello Li
    ...                                                                      
    test_hello.py Hello Li
    ...
    
    

  • これで、サードパーティ製pytestプラグインの作成はすべて完了しました.キーはpytest hook付きmoduleを作成した後、setup.pyにpytest 11のentry_を提供することです.pointsでいいです.
    ##プラグインの読み込み順序は、conftest形式のローカルプラグインがある以上、外部サードパーティプラグインがある以上、プラグインの読み込み順序はどのようなものなのかを聞くかもしれません.公式文書で規定されている順序は以下のplugin-orderです.
  • build-inプラグインをロードします.つまりpytest内蔵の_pytestフォルダのプラグイン
  • 外部にインストールされているプラグインをロードします.setuptoolsが提供するentry_を検索します.pointsが見つけたモジュール
  • -pパラメータで指定したプラグインをロードします.コマンドラインの-pパラメータを予めスキャンすることにより、指定されたplugin
  • をロードする
  • conftest.pyにロードされたプラグイン
  • -traceconfigオプションを使用して、ロードされたすべてのプラグインを表示できます.
    Pytestのhook管理コア:Pluggyフレームワーク
    上記のプラグインは、pytestが定義したhookを呼び出すものです.どんなホックが使えますか?参考hook-reference
    これらのhookはpytest_で冒頭にpytestのbootstrap,collection,test run,reportなどの各段階を網羅し,pytestフレームワークの各時期に柔軟に切り込み,カスタム機能を実現できる.
    本質的にpytestフレームワークはhookの集合であり,異なる時期に異なるhookを実行し,各hookは1:Nのpython関数呼び出しである.
    具体的にhook管理を担当するコードはPluggyフレームワークに統合され、Pytestフレームワークhook管理の核心はこのPluggyである.
    このPluggyフレームワークの使い方を例に挙げてみましょう.
    import pluggy
    
    hookspec = pluggy.HookspecMarker("myproject") #hook  
    hookimpl = pluggy.HookimplMarker("myproject") #hook    
    
    
    class MySpec(object):
        """hook   """
    
        @hookspec
        def myhook(self, arg1, arg2):
            pass
    
    
    class Plugin_1(object):
        """hook   1"""
    
        @hookimpl
        def myhook(self, arg1, arg2):
            #   
            print("inside Plugin_1.myhook()")
            return arg1 + arg2
    
    
    class Plugin_2(object):
        """hook   2"""
    
        @hookimpl
        def myhook(self, arg1, arg2):
            #   
            print("inside Plugin_2.myhook()")
            return arg1 - arg2
    
    
    #     PluginManager
    pm = pluggy.PluginManager("myproject")
    
    #   hook  
    pm.add_hookspecs(MySpec)
    
    #   hook   
    pm.register(Plugin_1())
    pm.register(Plugin_2())
    
    #      hook
    results = pm.hook.myhook(arg1=1, arg2=2)
    
    print(results)
    # Output
    # inside Plugin_2.myhook()
    # inside Plugin_1.myhook()
    # [-1, 3]
    
    
    
  • Pluggyのコアは、3つのクラスHookspecMarker、HookimplMarker、PlugingManagerです.その主な論理はHookspecMarkerでhookをマークし、HookimplMarkerでhookの1つ以上の実装をマークすることである.最終的には、hookの登録(pm.add_hookspecs)、hook実装の登録(pm.register)、hookの実行(pm.hook.myhook)など、PluginManagerでhookを管理する.
  • プロジェクト全体でグローバル一意のプロジェクトNameを使用する必要があります.この例では「myproject」
  • です.
  • hook実行時、その複数のインプリメンテーションはregisterの順に進んでから出るので、ここではPlugin_を先に呼び出す2の実装、Plugin_の再呼び出し1にあります.順序を変更するには、@hookimplを呼び出すときにパラメータtryfirst(キューの末尾に挿入)またはtrylast(キューの先頭に挿入)を入力します.
  • hookは、呼び出しが空の結果セット[]を返す実装がなくてもよい.利用可能pm.hook.myhook.get_hookimpls()はhookの実装オブジェクトを取得する.

  • Pluggyソースコード解読plugyソースコード解読参照
    カスタムhook
    pytestフレームワークでは、hook spec hookアクセサリーラベルは@hookspec,@hookimpl、PlugginManagerインスタンスオブジェクトはconfig.pluginmanagerです.こうしてみるとラベルもあるし、PluginManagerオブジェクトもあるので、私たちは完全に自分でhookを書くことができます.writinghooks
  • hookspecはモジュールを書いて、@hookspecであなたのすべてのhookspecを宣言します.
    """
    Filename: hooks.py 
    Description: hook   module
    """
    
    from pytest import hookspec
    
    @hookspec
    def pytest_my_hook(config):
        """my  hook"""
    
    
  • hookimplはpluginを書いてあなたのhookを含んで
    """
    Filename: hooks_impl.py 
    Description: hook   
    """
    
    from pytest import hookimpl
    
    class Plugin(object):
    
        @hookimpl
        def pytest_my_hook(config):
            print('my first hook')
    
    
  • を実現します
  • PluginManagerのadd_hookspecs,register用PluginManagerオブジェクトadd_hookspecs,registerはhook申明とhook実装を追加する.pytestは直接pytest_がありますaddhooksが利用できます.
    """
    Filename: conftest.py 
    """
    from . import hooks
    from .my_plugin import myPlugin
    
    def pytest_addhooks(pluginmanager):
        pluginmanager.add_hookspecs(hooks)
        pluginmanager.register(MyPlugin())
    
    
  • カスタムホックの使用準備が整いましたので、カスタムホックを使ってみてください.使用する鍵はpytestのconfigオブジェクトを見つけ、config.pluginmanager.hookで私たち自身のhookをcallすることです.(config.hookでcall、pytestでconfig.pluginmanager.hook=config.hook)configを取るには主に2つの方法があります.1つはpytestが持参したhookのパラメータからconfigオブジェクトを取ることです.2つはfixtureであれば、直接pytestconfigを導入してconfigオブジェクトを取ることができます.
    #       pytest   hook   config
    def pytest_runtest_call(item):
        item.config.hook.pytest_my_hook(config=item.config)
    
    #     fixture      pytestconfig  pytestconfig
    @pytest.fixture('session')
    def my_fixture(pytestconfig):
        print('my_fixture')
        result = pytestconfig.hook.pytest_my_hook(config=pytestconfig)
    
    
    注意すべき点は次の点です.
  • pytestのhookはpyest_で先頭、そうでないとpytestフレームワークは認識されません
  • call hookの場合、キー値ペアでパラメータを渡す必要があります.「TypeError:hook calling supports only keyword arguments」

  • 締めくくり
    ローカルプラグインと外部プラグインを含むpytestプラグインの書き方を検討した.プラグインを書く核心はhookを書くことです.hookはpytestが持っているhookメソッドを書き換えることができ、pytestが適切なタイミングでhookを呼び出すか、hookをカスタマイズしてpytestフレームワークに登録し、自分でhookを呼び出すタイミングを手配することができます.