pytest-benchmarkでboto3&motoを含めたClassBasedなコードを計測する方法について試行錯誤してみた
はじめに
Pythonで定期的にベンチマークを図りたい場合にpytest-benchmarkという選択肢があります。既存のテストに混在させた場合でも、実行時のオプション指定で動作切り分けも可能です。
ただ、pytest-benchmarkをboto3 + moto + ClassBased-Testで利用したケースについては、調べた限りではたどり着くことができませんでした。以下の記事を参考にしつつ、試行錯誤した結果をまとめてみました。
Python: pytest-benchmark でベンチマークテストを書く - CUBE SUGAR CONTAINER
pytest-benchmarkの導入から実行用指定まで
とくに変わった指定はありません。念の為benchmarkだけを動かす前提とします。
導入
pipenv install --dev pytest-benchmark
実行用指定
pytest -v --benchmark-only
boto3 + motoと併用
簡単なサンプルコードを用意してみました。
import pytest import boto3 from moto import mock_s3 class TestDemoClass: def setup_class(cls): pass def teardown_class(cls): pass def setup_method(self, name): pass def teardown_method(self, name): pass @mock_s3 def process_main(self, duration=0.000001): # any process client = boto3.client('s3') .. return True def test_benchmark(self, benchmark): ret = benchmark.pedantic(self.process_main, rounds=100, iterations=100) assert ret is True
% pytest -v --benchmark-only ================= test session starts ========================= platform darwin -- Python 3.7.3, pytest-5.0.1, py-1.8.0, pluggy-0.12.0 -- /XXXXX/.venv/bin/python3.7 cachedir: .pytest_cache benchmark: 3.2.2 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) rootdir: /XXXXX, inifile: setup.cfg, testpaths: tests/ plugins: benchmark-3.2.2, cov-2.7.1 collected 1 items test_demo.py::TestDemoClass::test_benchmark PASSED ----------------- benchmark: 1 tests ------------------------- Name (time in ms) Min Max Mean StdDev Median IQR Outliers OPS Rounds Iterations -------------------------------------------------------------- test_benchmark 17.7854 30.1732 19.3668 1.2481 19.3553 0.7839 6;1 51.6349 100 100 -------------------------------------------------------------- ================= 1 passed in 196.20 seconds ============================
pytest-benchmarkを次の順番で実装・実行しています。
- benchmarkを図る処理を実装したメソッド
process_main
(非テスト)を用意する - 1で実装したメソッドを呼ぶbenchmark実行用testメソッド
test_benchmark
を用意する - 2で実装したメソッドを実行する
また、試行回数と呼び出し回数も設定する場合にどうなるかも記載するため、pedanticでの呼び出しにしています。process_mainでTrueを返しているのは、benchmark完了をもってassertを実行するためです。
process_mainの中で非test実施用メソッドを実行したい場合は、self.XXX()
の呼び出しで問題ありません。
実行時
boto3による想定外なAWSへのアクセスが発生してしまわないようにするため、本番環境以外ではaws profile用変数にサンプル的な文字列を設定することをおすすめします。
また、実行時間が超過することも自明であるため、CircleCIの設定ファイルには忘れずに--benchmark-skip
を指定しておきましょう。
pipenv run pytest --benchmark-skip
気をつけるべき点
conftest.py等からの@pytest.fixture
呼び出し、及び@pytest.mark.parametrize
でのfixture分離実装の併用が効きませんでした。
まとめ
benchmark用処理は特に扱いに関して慣例的なものがなく、コードがまちまちなど管理し辛い点も発生しやすい代物です。ですが、pytest-benchmarkによるtestコードベースでの管理であれば、実行手段の絞り込み等も含めて共通認識は維持しやすいでしょう。
boto3の処理についてはAWS側のレスポンスに大きく左右されますが、それ以外のコードでのベンチマークを計って置きたい場合には有効な手段だと思います。