統合のための良い習慣

pip でパッケージをインストール

開発では、仮想環境に venv を使用し、アプリケーションや依存関係、および pytest パッケージ自体のインストールには pip を使用することをお勧めします。 これにより、コードと依存関係がシステムの Python インストールから分離されます。

Packaging Python Projects に記載されているように、リポジトリのルートに pyproject.toml ファイルを作成します。 最初の数行は次のようになります:

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "PACKAGENAME"
version = "PACKAGEVERSION"

ここで、PACKAGENAMEPACKAGEVERSION はそれぞれパッケージの名前とバージョンです。

次に、同じディレクトリから実行して「編集可能」モードでパッケージをインストールできます:

pip install -e .

これにより、ソースコード (テストとアプリケーションの両方) を変更して、必要に応じてテストを再実行できます。

Python テストの発見に関する規則

pytest は次の標準的なテストの発見を実装します:

  • 引数が指定されていない場合、収集は testpaths (設定されている場合) または現在のディレクトリから開始されます。 あるいは、コマンドライン引数でディレクトリ、ファイル名、ノード ID の任意の組み合わせを使用できます。

  • norecursedirs に一致しない限り、ディレクトリを再帰的に検索します。

  • それらのディレクトリで test package name でインポートされる test_*.py または *_test.py ファイルを検索します。

  • これらのファイルからテスト項目を収集します:

    • クラスの外部にある test で始まるテスト関数またはメソッド。

    • Test で始まるテストクラス内の test で始まるテスト関数またはメソッド (__init__ メソッドなし)。 @staticmethod@classmethods で装飾されたメソッドも対象となります。

テストの発見をカスタマイズする方法の例については 標準 (Python) テストディスカバリーの変更 を参照してください。

Python モジュール内では、pytest は標準の unittest.TestCase サブクラス化技法を使用してテストを発見します。

テストのレイアウトを選択

pytest は 2 つの一般的なテストレイアウトをサポートしています:

アプリケーションコードの外部にテストを配置

実際のアプリケーションコードの外部の別のディレクトリにテストを配置することは、多くの機能テストがある場合や、その他の理由で実際のアプリケーションコードとテストを分離しておきたい場合に便利です (多くの場合良い考えです)。

pyproject.toml
src/
    mypkg/
        __init__.py
        app.py
        view.py
tests/
    test_app.py
    test_view.py
    ...

これには次のような利点があります:

  • pip install . を実行後、インストールされたバージョンに対してテストを実行できます。

  • pip install --editable . を実行後、編集可能なインストールでローカルコピーに対してテストを実行できます。

新しいプロジェクトでは、importlibimport mode を使用することを推奨します (詳しくは which-import-mode を参照してください)。 そのためには、pyproject.toml に次のように追加してください:

[tool.pytest.ini_options]
addopts = [
    "--import-mode=importlib",
]

一般的に、特にデフォルトのインポートモード prepend を使用している場合は、src レイアウトを 強く 推奨します。 ここでは、アプリケーションのルートパッケージはルートのサブディレクトリ (mypkg ではなく src/mypkg/) に配置されます。

このレイアウトは、多くの一般的な落とし穴を防ぎ、多くの利点があります。 それらは Ionel Cristian Mărieș による優れた blog post でさらに詳しく説明されています。

注釈

編集可能インストールを使用せず、上記のように src レイアウトを使用している場合は、ローカルコピーに対して直接テストを実行するために、Python のモジュール検索パスを拡張する必要があります。 これは PYTHONPATH 環境変数を設定することでアドホックに行えます:

PYTHONPATH=src pytest

あるいは、pythonpath の設定変数を使用し、pyproject.toml に次のように追加することで恒久的に設定できます:

[tool.pytest.ini_options]
pythonpath = "src"

注釈

編集可能インストールと src レイアウトを使用しない場合 (ルートディレクトリに mypkg を直接配置) は、Python がデフォルトで現在のディレクトリを sys.path に入れることを利用して、python -m pytest を実行し、ローカルコピーに対して直接テストを実行できます。

pytestpython -m pytest を呼び出す違いの詳細については pytest と python -m pytest の呼び出し を参照してください。

アプリケーションコードの一部としてのテスト

アプリケーションパッケージにテストディレクトリを組み込むことは、テストとアプリケーションモジュールが直接的に関連しており、アプリケーションと一緒に配布したい場合に便利です:

pyproject.toml
[src/]mypkg/
    __init__.py
    app.py
    view.py
    tests/
        __init__.py
        test_app.py
        test_view.py
        ...

この方式では、--pyargs オプションを使用してテストを簡単に実行できます:

pytest --pyargs mypkg

pytestmypkg がインストールされている場所を検出し、そこからテストを収集します。

このレイアウトは、前のセクションで言及されている src レイアウトとも併用できます。

注釈

アプリケーションに名前空間パッケージ (PEP420) を使用できますが、pytest は依然として __init__.py ファイルの有無に基づいて test package name の発見を行います。 上記の 2 つの推奨レイアウトのいずれかを使用してディレクトリから __init__.py を省略した場合でも動作するはずです。 ただし、「組み込みテスト」ではアプリケーションコードにアクセスするために絶対インポートが必要になります。

注釈

prepend および append インポートモードでは、pytest がファイルシステムを再帰的に検索する際に "a/b/test_module.py" というテストファイルを見つけると、次のようにインポート名を決定します:

  • basedir を決定する: これは最初に「上向き」(ルート方向) に移動して __init__.py が存在しないディレクトリです。 例えば、ab の両方に __init__.py が含まれている場合は、a の親ディレクトリが basedir になります。

  • テストモジュールを完全修飾インポート名でインポートできるようにするために、sys.path.insert(0, basedir) を実行します。

  • パス区切り / をピリオド . に変換し、import a.b.test_module を行います。 これはディレクトリ名やファイル名をインポート名に直接対応させる規則に従う必要があることを意味します。

この少し複雑なインポート手法を用いている理由は、大規模なプロジェクトでは複数のテストモジュールが互いにインポートし合う可能性があり、正規のインポート名を導くことによってテストモジュールが 2 回インポートされるといった問題を回避するのに役立つためです。

--import-mode=importlib を使用すると、pytest が sys.path を変更する必要がないため、状況がはるかに単純になり、予期しない事態が起こりにくくなります。

インポートモードの選択

歴史的な理由から、pytest は新しいプロジェクトに推奨している importlib インポートモードではなく、デフォルトで prepend import mode を使用します。 その理由は prepend モードの動作にあります:

フルパッケージ名を導くパッケージがないため、pytest はテストファイルをトップレベルモジュールとしてインポートします。 最初の例 (src layout) にあるテストファイルは、tests/sys.path に追加することで、test_apptest_view というトップレベルモジュールとしてインポートされます。

これは importlib インポートモードに比べて不利な点をもたらします。 つまり、テストファイルの名前を 一意 である必要があります。

同名のテストモジュールを必要とする場合、回避策として tests フォルダとそのサブフォルダに __init__.py ファイルを追加して、それらをパッケージとして扱うことができます:

pyproject.toml
mypkg/
    ...
tests/
    __init__.py
    foo/
        __init__.py
        test_view.py
    bar/
        __init__.py
        test_view.py

これにより、pytest はそれらのモジュールを tests.foo.test_view および tests.bar.test_view として読み込み、同じ名前のモジュールを使用できます。 しかし、ここで微妙な問題が発生します。 tests ディレクトリからテストモジュールを読み込むために、pytest はリポジトリのルートを sys.path の先頭に追加し、その副作用として mypkg もインポート可能になってしまうのです。

tox のようなツールを使用して仮想環境でパッケージをテストしている場合、リポジトリのローカルコードではなく、インストールされた バージョンのパッケージをテストしたいので、これは問題となります。

importlib インポートモードは、テストモジュールをインポートする際に sys.path を変更しないため、上記の問題は一切発生しません。

tox

作業が完了し、実際のパッケージがすべてのテストに合格していることを確認したい場合は、仮想環境テスト自動化ツールである tox を調べてみることをお勧めします。 tox は、事前に定義された依存関係を持つ仮想環境を設定し、事前に構成されたテストコマンドをオプション付きで実行するのに役立ちます。 インストールされたパッケージに対してテストを実行し、ソースコードのチェックアウトに対してテストを実行しないため、パッケージングの問題を検出するのに役立ちます。

setuptools を介して実行しない

setuptools との統合は 推奨されていません。 つまり、python setup.py testpytest-runner を使用してはいけませんし、将来的には動作しなくなる可能性があります。

これは、setuptools の非推奨機能に依存しており、pip のセキュリティメカニズムを破壊する機能に依存しているため、非推奨とされています。 たとえば、 'setup_requires' と 'tests_require' は pip --require-hashes をバイパスします。 詳細と移行手順については、pytest-runner notice を参照してください。 pypa/setuptools#1684 も参照してください。

setuptools は テストコマンドを削除する予定です

flake8-pytest-style でのチェック

プロジェクトで pytest が正しく使用されていることを確認するために、flake8-pytest-style flake8 プラグインを使用すると便利です。

flake8-pytest-style は、フィクスチャ、テスト関数名、マーカーの誤った使用など、pytest コードの一般的な間違いやコーディングスタイルの違反をチェックします。 このプラグインを使用することで、これらのエラーを開発プロセスの早い段階で検出し、pytest コードが一貫しており、メンテナンスしやすいことを確認できます。

flake8-pytest-style によって検出されるリントのリストは、その PyPI ページ にあります。

注釈

flake8-pytest-style は公式の pytest プロジェクトではありません。 一部のルールは、 @pytest.fixture よりも @pytest.fixture() を使用するなど、特定のスタイルの選択を強制しますが、プラグインを設定して好みのスタイルに合わせることができます。