きり丸の技術日記

技術検証したり、資格等をここに残していきます。

【Scrap】pytestについて

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という箇所については、一苦労が必要です。次の処理箇所がpytestshort test summary infoのメッセージを作成している箇所です。メインのエラーメッセージをstrで渡すこともできますが、その場合はAttributeErrorの処理に入ってしまって値が取得できません。

try:
    # Type ignored intentionally -- possible AttributeError expected.
    msg = rep.longrepr.reprcrash.message  # type: ignore[union-attr]
except AttributeError:
    pass

そのため、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

もうちょっと頭のいいやり方があったかもしれないのですが、上のようにしてlogreprstrではなくExceptionChainReprで渡せばshort test summary infoに値を渡せました。

その件はこちらのPull Requestに入れています。

オプションについて

とりあえず実行時につけているオプション。このセクションには中身は無いです。

# -p no:warnings はPython側の機能です
addopts = ["-p no:warnings", "-p no:logging", "--last-failed", "-s"]

もっと便利になりそうなオプションがあったら追加します。

量が多くて読み切れていないです。

Pytest-Checkについて

PythonAssertion Rouletteにならないように回避するライブラリ。

JavaでのSoftAssertionsと同じような処理をします。

こちらの紹介記事に関しては、PullRequestがmergeされたら記載する予定です。

ソースコード

なし

終わりに

正直。pytest-checkへのPullRequestを作成したときに色々と調べたメモです。今後もPytest関連のライブラリにコントリビュートするかは置いておいて、一瞬だけ使用する知識としておくのももったいないのでScrapという形で記事化しました。

参考文献