pytestプラグイン探索-pytest-xdist
4868 ワード
背景
pytestプラグインの開発を頻繁に行うと、次のようなコードクリップがたくさん見られます.
def pytest_configure(config):
...
# prevent ... on slave nodes (xdist)
if not hasattr(config, 'slaveinput'):
...
これらのコードは
pytest-xdist
というプラグインと互換性があるためです.このプラグインを簡単に紹介します.pytest-xdistこのプラグインはユーザーがテストを同時に実行することを許可します(プロセスレベルの同時実行).主な開発者はpytestの現在の核心開発者Bruno Oliveiraであり、執筆時までに371個のstarがあり、4150個のプロジェクトに応用された.なお、プラグインは試験用例の実行順序を動的に決定するため、各試験が各独立スレッドで正しく実行できることを保証するために、用例の著者は試験用例の独立性を保証すべきである(これも試験用例設計のベストプラクティスに合致する).プロセス
ここではプラグインの実行原理を紹介し、簡単な翻訳を行い、一部の注釈を加えた.
ほとんどの分布式システムと似ており、
xdist
にはmasterとworkerの概念がある.masterはテストタスク全体のスケジューリング、テストレポートなどの仕事を担当し、workerは実際にテストを実行する宿主プロセスである.具体的なテスト実行の流れは以下の通りです.
xdist
は1つ以上のworkerプロセスをspawnする.マスターとworker間の通信はexecnetとそのgatewaysに基づいている.workerの解釈器はローカルまたはリモートであることができる.pytest runner
オブジェクトである.workersは、完全なtest collectionプロセスを実行し、結果をmasterに返します(master自体はテスト収集を行いません).FAQでは、各node上で単独でテスト収集を実行するのは、master上でテスト収集を実行する場合、workerがプロセスレベルであるため、多くのシーケンス化処理が必要であるからである.これにより、問題が複雑化する、pytestのメンテナンスが困難になる.
dist-mode
がeachである場合、masterは完全なリストを各ノードに送信するだけでよい.dist-mode
がloadである場合、masterは約25%のテスト項目を各workerにポーリングする.残りのテスト項目はworkersがテストを実行した後に配布するのを待っています.以下を参照してください.注意:
pytest_xdist_make_scheduler
というhookは、カスタム配布ロジックを実現するために使用することができる.pytest_runtestloop
を書き換えた:pytestのデフォルト実装は基本的にsession
というオブジェクトで収集するすべてのテスト項目をループ実行するが、xdist
では、workersは実際にはmasterが実行する必要があるテスト項目を送信するのを待っている.workerがテストタスクを受け取ると、pytest_runtest_protocol
が順次実行する.注意すべき1つの詳細は、ワークersがpytest_runtest_protocol(item, nextitem)
hookのパラメータ要件と互換性を持つために、少なくとも1つのテスト項目があるタスクキューに常に保持されなければならないことである.nextitem
をhookに渡すために、workerは最後のテスト項目を実行する前にmasterのより多くの命令を待つ.より多くの試験項目が受信場合、pytest_runtest_protocol
パラメータが決定できるため、nextitem
を安全に実行することができる.「shutdown」信号を受信と、nextitem
のパラメータをNone
に設定し、pytest_runtest_protocol
を実行する.pytest_runtest_logstart
やpytest_runtest_logreport
のような他のpytest hooksが正常に実行できる.master(loadのdist-mode
の場合)は、ノードが1つのテストを実行する後、テスト実行時間の長さおよび各ノードの残りのテスト項目に基づいて、このノードにより多くのテスト項目を送信か否かを総合的に決定する.pytest_runtest_logreport
などのイベントを処理する必要がある.Best Practice
pytest-xdist
の実現原理を理解した後、開発したプラグインが正常に協力できることを保証するために(仕方なく、このプラグインは流行しすぎます)、プラグイン開発時に提案します.report
クラスのプラグインについて、通常、masterノード上で一度初期化し、各reportオブジェクトを処理するだけでよい.hasattr(config, 'slaveinput')
を判断することによってworkerノードであるか否かを決定し、処理相論理を区別することができる.pytest_runtest_makereport
などのhooksでは、インスタンス化されたオブジェクトがシーケンス化されたときにエラーを報告するため、オブジェクトのインスタンス化操作を回避します.たとえば、いくつかのテストでは、次のconftest.py
ファイルが使用されています.import pytest
class SomeThing(object):
pass
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
report = outcome.get_result()
report.something = SomeThing()
def pytest_runtest_logreport(report):
print('something: %r' % report.something)
では、pytest-nを使用して実行すると、このようなエラーが報告されます.INTERNALERROR> raise DumpError("can't serialize {}".format(tp)) INTERNALERROR> execnet.gateway_base.DumpError: can't serialize
正しい方法は、保存する必要があるデータを
report
オブジェクトに保存することです.例えば、次のコードは、テスト実行のタイムスタンプをreport
オブジェクトに保存することができます.その後、workerはreport
をmasterノード:def pytest_runtest_makereport(item, call):
outcome = yield
report = outcome.get_result()
if report.when == "call":
report.call_start = call.start
report.call_end = call.stop
datetime
のようなタイプも直接シーケンス化できないことが発見する、この場合はtimestampとして保存してからタイプ変換操作を行うことを考慮することができる.pytest_runtest_makereport
のようなhook関数をクラスに書く方法もあるが、このクラスがmasterノード上でのみインスタンス化されている場合、無効なhook関数が書かれていることに相当し、プログラムはエラーを報告することはないので、特に注意しなければならない.要するに、
config
オブジェクトはプロセス間で独立しているが、report
オブジェクト間の値は互いに同期することができるが、同期オブジェクトは避けなければならない.