pytest-xdistとは
pytest-xdistはテストの並列実行・分散実行に使うpytestのプラグインです。
これをインストールし、実行時に並列ノード数をオプションとして渡すだけで、勝手にテストを振り分けて実行してくれます。
本記事ではpytest-xdistを実際に使ってみて分かった便利な使い方、オプションなどをメモとしてまとめます。
なお、私はpytestをユニットテストではなく、主にSeleniumやPlaywrightを使ったUIテスト用途に利用しているため、それに特化した内容になっていることをご承知おきください。
内容は随時更新します。
インストール
pip install pytest-xdist
テストの並列実行
pytest -n 4 test.py
-nのあとにテストを並列実行するノード数(ワーカー数)を入力します。
数値の代わりにautoを指定すると利用可能なCPUのコア数が自動的に代入されます。
pytest -n auto test.py
コア数以上の値を代入すればノードをその分だけ展開することも可能ですが、CPUのコア数の制限を受けます。
展開したノード上で厳密な並列処理はできないことに注意ください。
ノードの特定
pytest-xdistの実行時にはworker_idfixtureが呼び出せます。
これを使うと、テストを実行したノードの特定が可能です。
def test_worker_id(self, user_id):
hoge = f"このテストは{user_id}で実行されています"
# fixture user_idにworker id(gw0, gw1, ...)が入る
print文の出力
pytest-xdistを使用すると、pytestで利用可能な-s/--capture=noオプションが無効化され、そのままではprint文を表示することができなくなります。
print文を表示させたい場合は標準エラーに出力してください。
import sys
def test_print_worker(self, user_id):
message = "(ワーカーごとの処理結果)"
print(f"{user_id}: {message}", file=sys.stderr)
オプション入力の省略(設定ファイル)
pytest-xdist実行時のオプションが固定化されている場合、設定ファイルpyproject.tomlを作成し、以下のように記載することで毎回のオプション入力を省略できます。
[tool.pytest.ini_options]
addopts = "-n auto"
pytestの設定ファイルにはpyproject.tomlの他にpytest.iniなども利用できます。
poetryなど他ツールとの一元管理ができるpyproject.tomlに優位性があると考え、私はpyproject.tomlによる設定を採用しています。
https://docs.pytest.org/en/7.0.x/reference/customize.html
全ノードでの同一テストの実行
UIテストでは、稀に「同時にnブラウザからアクセスする」といった負荷テスト的な項目を確認する場合があります。
この際はpytest-xdistのdistオプションを以下のように指定すれば全ノードで同じテストを実行できます。
pytest -n 4 --dist each test.py
負荷テストそれ自体は基本的にAPIリクエスト等を経由して実現するほうが効果的です。
あくまでUI経由で実行する合理的な理由がある際にのみこの方法を採用するといいと思います。
余談: pytest-parallelについて
pytestでテストを並列実行できるプラグインとして、pytest-parallelというものも存在します。
pytest-parallelのpytest-xdistに対する優位点は以下の3点であると述べられています。
pytest-parallel is better for some use cases (like Selenium tests) that:
- can be threadsafe
- can use non-blocking IO for http requests to make it performant
- manage little or no state in the Python environment
スレッドセーフであるため、並列実行においてはpytest-parallelの方がより安全であると言えます。
しかし一方で使用者の多さ、プラグインの更新頻度、Issueに対するレスポンスの良さなどはpytest-xdistの方が勝る印象です。
私はpytest-xdistを優先的に利用しています。