ZennのScrapsと同じような感覚の記事。間違っている可能性は十分にあります。
今回の記事ではpytestについて自分が調べたことをまとめます。
環境
- Python
- 3.7.9
- pytest
- 7.4.3
- pytest-check
- 2.2.2
Pytestについて
処理順番
pytestではヒットした順番で順次テストするのではなく、collectionというテスト対象のメソッドを集めた過程を経てテストを実行します。この実行前のcollectionで先頭にTestが付いているクラス、test_がついているメソッドを探しています。感覚的には、200テストを探すのに1秒くらいかかるので、テストケースが増えれば増えるほど起動までに時間がかかります。
--last-failedというオプションを付けて実行すると、前回のセッションで失敗したテストだけを実行してくれます。しかし地味にcollectionに時間がかかるので、失敗したテストが複数ファイルに跨っていない限りは、失敗したファイルだけで実行したほうが早く済みます。このオプションを付与しているときは、collection対象を減らすというコントリビュートチャンスかも。
--continue-on-collection-errorsというオプションをつけると、通常はcollection処理中に一部テストで構文エラーがあった時にはテストを実行しませんが、付与すると構文エラーがあっても他のテストを実行してくれます。基本的にはIDEやプラグインでしか使わないオプションだと思います。
テストレポートについて
メインのテストレポートについては、hook機能を使用すると書き換えられます。
@pytest.hookimpl(hookwrapper=True, trylast=True) def pytest_runtest_makereport(item, call): pass
使い方は別のライブラリであるpytest-checkを見るのがいいかも。
厄介なのがshort test summary infoという箇所については、一苦労が必要です。次の処理箇所がpytestのshort test summary infoのメッセージを作成している箇所です。メインのエラーメッセージをstrで渡すこともできますが、その場合はAttributeErrorの処理に入ってしまって値が取得できません。
try: # Type ignored intentionally -- possible AttributeError expected. msg = rep.longrepr.reprcrash.message # type: ignore[union-attr] except AttributeError: pass
- short test summary info の
- XXXの部分
そのため、pytestに合わせて次のように値を渡す必要がありました。
# ASIS try: raise AssertionError(report.longrepr) except AssertionError: excinfo = ExceptionInfo.from_current() call.excinfo = excinfo
# TOBE from _pytest.reports import ExceptionChainRepr from _pytest._code.code import ExceptionRepr, ReprFileLocation try: raise AssertionError(report.longrepr) except AssertionError as e: excinfo = ExceptionInfo.from_current() reprcrash = ReprFileLocation(item.nodeid, 0, str(e)) reprtraceback = ExceptionRepr(reprcrash, excinfo) chain_repr = ExceptionChainRepr([(reprtraceback, reprcrash, str(e))]) report.longrepr = chain_repr call.excinfo = excinfo
もうちょっと頭のいいやり方があったかもしれないのですが、上のようにしてlogreprをstrではなくExceptionChainReprで渡せばshort test summary infoに値を渡せました。
その件はこちらのPull Requestに入れています。
オプションについて
とりあえず実行時につけているオプション。このセクションには中身は無いです。
# -p no:warnings はPython側の機能です addopts = ["-p no:warnings", "-p no:logging", "--last-failed", "-s"]
もっと便利になりそうなオプションがあったら追加します。
量が多くて読み切れていないです。
Pytest-Checkについて
PythonでAssertion Rouletteにならないように回避するライブラリ。
JavaでのSoftAssertionsと同じような処理をします。
こちらの紹介記事に関しては、PullRequestがmergeされたら記載する予定です。
ソースコード
なし
終わりに
正直。pytest-checkへのPullRequestを作成したときに色々と調べたメモです。今後もPytest関連のライブラリにコントリビュートするかは置いておいて、一瞬だけ使用する知識としておくのももったいないのでScrapという形で記事化しました。