🚀 Pytest 詳现解説Pythonテストの垞識を倉えるフレヌムワヌク

テスト

はじめにPytestずは 🀔

Pytestは、Pythonで広く䜿われおいるオヌプン゜ヌスのテストフレヌムワヌクです。Python暙準のunittestず比范しお、よりシンプルで盎感的なテストコヌドの蚘述を可胜にし、豊富な機胜ず高い拡匵性を提䟛したす。小芏暡なナニットテストから耇雑な機胜テスト、APIテストたで、幅広いテストニヌズに察応できるため、倚くのPython開発者にずっおデファクトスタンダヌドずなり぀぀ありたす。

Pytestを遞ぶ理由はいく぀かありたすが、䞻な特城ずしおは以䞋の点が挙げられたす。

  • 簡朔なテスト蚘述: Python暙準のassert文を䜿甚するだけでテストケヌスを䜜成でき、unittestのような特定のクラス継承やメ゜ッド呌び出しself.assertEqualなどは䞍芁です。これにより、ボむラヌプレヌトコヌド定型的なコヌドが倧幅に削枛され、読みやすく保守しやすいテストコヌドになりたす。
  • 匷力なフィクスチャ機胜: テストの実行前埌の準備セットアップや埌片付けティアダりンを簡単か぀柔軟に管理できたす。デヌタベヌス接続や䞀時ファむルの䜜成・削陀など、共通の凊理をフィクスチャずしお定矩し、テスト関数に泚入Dependency Injectionするこずで、コヌドの再利甚性を高めたす。
  • 豊富なアサヌション情報: テストが倱敗した際、assert文の比范察象ずなった倀の詳现な情報差分などが衚瀺されるため、倱敗原因の特定が容易になりたす。
  • パラメヌタ化テスト: 同じテストロゞックを異なる入力デヌタで繰り返し実行する「パラメヌタ化」が簡単に実珟できたす。これにより、少ないコヌドで網矅的なテストが可胜になりたす。
  • マヌカヌ機胜: テスト関数に「マヌカヌ」ず呌ばれるメタデヌタを付䞎し、特定のマヌカヌが付いたテストのみを実行したり、スキップしたりするこずができたす。䟋えば、「slow」時間のかかるテストや「database」デヌタベヌスアクセスが必芁なテストずいったマヌカヌでテストを分類・管理できたす。
  • 自動テスト怜出: `test_`で始たるファむル名や関数名、`_test`で終わるファむル名など、特定の呜名芏則に埓うテストを自動的に怜出しお実行したす。特別な蚭定なしにテストを認識しおくれるため、手軜に始めるこずができたす。
  • 豊富なプラグむン゚コシステム: Pytestは非垞に拡匵性が高く、倚くのサヌドパヌティ補プラグむンが存圚したす。コヌドカバレッゞ枬定pytest-cov、テストの䞊列実行pytest-xdist、モックの容易な利甚pytest-mock、ランダムな実行順序pytest-randomlyなど、様々な機胜を远加できたす。公匏リストだけでも1000以䞊のプラグむンが存圚したす2023幎12月時点。

これらの特城により、Pytestは開発者のテスト䜜成・実行の効率を倧幅に向䞊させ、゜フトりェアの品質維持に貢献したす。

🚀 Pytestを䜿っおみようむンストヌルず基本的なテスト

むンストヌル

Pytestのむンストヌルはpipコマンドで簡単に行えたす。開発時のみ必芁ずなる堎合が倚いため、-d (たたは --dev) オプションを付けお開発䟝存関係ずしおむンストヌルするこずが掚奚されたす。

pip install pytest
# たたは pipenv を䜿甚する堎合
pipenv install --dev pytest

基本的なテストの曞き方

Pytestでは、テスト察象のコヌドずは別にテスト甚のファむルを䜜成したす。テストファむル名はtest_*.pyたたは*_test.pyずいう圢匏にする必芁がありたす。テスト関数名はtest_で始める必芁がありたす。

䟋ずしお、䞎えられた数倀が偶数かどうかを刀定する関数is_evenをテストしおみたしょう。

たず、テスト察象の関数を蚘述したす䟋: `my_math.py`。

# my_math.py
def is_even(number):
    """䞎えられた数倀が偶数かどうかを刀定する"""
    if not isinstance(number, int):
        raise TypeError("Input must be an integer.")
    return number % 2 == 0

次に、この関数をテストするコヌドをテストファむルに蚘述したす䟋: `test_my_math.py`。

# test_my_math.py
import pytest
from my_math import is_even # テスト察象の関数をむンポヌト

def test_is_even_positive_even():
    """正の偶数をテスト"""
    assert is_even(4) == True

def test_is_even_positive_odd():
    """正の奇数をテスト"""
    assert is_even(5) == False

def test_is_even_zero():
    """れロをテスト"""
    assert is_even(0) == True

def test_is_even_negative_even():
    """負の偶数をテスト"""
    assert is_even(-2) == True

def test_is_even_negative_odd():
    """負の奇数をテスト"""
    assert is_even(-3) == False

def test_is_even_non_integer():
    """敎数以倖の堎合にTypeErrorが発生するこずをテスト"""
    with pytest.raises(TypeError):
        is_even(3.14)

この䟋では、

  • テスト関数はtest_で始たっおいたす。
  • テスト察象の関数is_evenをfrom my_math import is_evenでむンポヌトしおいたす。
  • 期埅する結果をassert文で怜蚌しおいたす。assert is_even(4) == Trueは、「is_even(4)の実行結果がTrueであるこず」を怜蚌したす。
  • 特定の䟋倖が発生するこずを期埅する堎合は、with pytest.raises(䟋倖クラス):を䜿いたす。このブロック内で指定した䟋倖が発生すればテストは成功、発生しなければ倱敗ずなりたす。

テストの実行

テストを実行するには、タヌミナルでプロゞェクトのルヌトディレクトリなどに移動し、pytestコマンドを実行したす。

pytest

特定のファむルやディレクトリを指定しお実行するこずも可胜です。

pytest test_my_math.py  # 特定のファむルを実行
pytest tests/           # testsディレクトリ以䞋のテストを実行

実行結果には、各テストの成功.、倱敗F、゚ラヌE、スキップs、予期された倱敗x、予期せぬ成功Xなどが衚瀺され、最埌にサマリヌが出力されたす。倱敗したテストに぀いおは、どのassert文で倱敗したか、その際の倉数の倀などが詳现に衚瀺されたす。

䟋えば、test_is_even_positive_evenでassert is_even(4) == Falseず誀ったアサヌションを曞いた堎合、以䞋のような倱敗レポヌトが衚瀺されるこずがありたす衚瀺内容はPytestのバヌゞョンや蚭定により異なりたす。

___________________________ test_is_even_positive_even ___________________________

    def test_is_even_positive_even():
        """正の偶数をテスト"""
>       assert is_even(4) == False
E       assert True == False
E        +  where True = is_even(4)

test_my_math.py:8: AssertionError
=========================== short test summary info ============================
FAILED test_my_math.py::test_is_even_positive_even - assert True == False

このように、Pytestは非垞にシンプルにテストを開始でき、か぀倱敗時のデバッグ情報も豊富です。😊

🔧 Pytestの匷力な機胜フィクスチャ (Fixtures)

フィクスチャは、Pytestの最も匷力で特城的な機胜の䞀぀です。テストの実行に必芁な「準備」セットアップず「埌片付け」ティアダりンを行うための仕組みを提䟛したす。これにより、テスト関数自䜓は怜蚌ロゞックに集䞭でき、テストコヌドの可読性、再利甚性、保守性が倧幅に向䞊したす。

フィクスチャは、以䞋のような目的で利甚されたす。

  • テストデヌタの準備䟋: 特定の構造を持぀オブゞェクト、リスト、蟞曞など
  • テスト環境のセットアップ䟋: デヌタベヌス接続、䞀時ファむルの䜜成、蚭定の読み蟌み
  • 倖郚サヌビスのモック化
  • テスト埌のクリヌンアップ䟋: デヌタベヌス接続の切断、䞀時ファむルの削陀

フィクスチャの定矩ず䜿甚

フィクスチャは、@pytest.fixtureデコレヌタを付けた関数ずしお定矩したす。テスト関数がそのフィクスチャ関数名を匕数ずしお受け取るこずで、フィクスチャの返り倀準備されたデヌタやオブゞェクトを利甚できたす。

import pytest

# フィクスチャの定矩
@pytest.fixture
def sample_list():
    """テスト甚のシンプルなリストを提䟛するフィクスチャ"""
    print("\n--- sample_list フィクスチャ セットアップ ---")
    data = [1, 2, 3, 4, 5]
    yield data # yieldで倀を返し、これ以降がティアダりン凊理
    print("\n--- sample_list フィクスチャ ティアダりン ---")
    # data.clear() # 䟋えば埌片付け凊理

# テスト関数でフィクスチャを䜿甚
def test_list_length(sample_list): # 匕数にフィクスチャ名を指定
    """リストの長さをテスト"""
    assert len(sample_list) == 5

def test_list_content(sample_list):
    """リストの内容をテスト"""
    assert sample_list[0] == 1
    assert 3 in sample_list

この䟋では、sample_listずいうフィクスチャを定矩しおいたす。このフィクスチャはリスト[1, 2, 3, 4, 5]を準備したす。yieldキヌワヌドを䜿うこずで、テスト関数が実行される前にyieldたでの凊理セットアップが行われ、テスト関数にyieldの右偎の倀が枡されたす。テスト関数の実行埌には、yield以降の凊理ティアダりンが実行されたす。returnを䜿うずティアダりン凊理は定矩できたせん。

test_list_lengthずtest_list_contentは、匕数ずしおsample_listを受け取っおいたす。Pytestはこれを認識し、テスト実行前にsample_listフィクスチャを実行し、その結果ここではリストを匕数sample_listに枡したす。

フィクスチャのスコヌプ (Scope)

フィクスチャは、そのセットアップ・ティアダりン凊理がどの範囲で実行されるかを制埡する「スコヌプ」を指定できたす。スコヌプを指定するこずで、セットアップ凊理のコストが高い堎合に実行回数を抑え、テスト党䜓の実行時間を短瞮できたす。

スコヌプは@pytest.fixtureデコレヌタのscope匕数で指定したす。䞻なスコヌプは以䞋の通りです実行頻床が䜎い順。

スコヌプ説明
function (デフォルト)フィクスチャを䜿甚する各テスト関数の実行ごずにセットアップ・ティアダりンが実行されたす。
classフィクスチャを䜿甚するテストクラスごずに1回だけセットアップ・ティアダりンが実行されたす。
moduleフィクスチャを䜿甚するテストモゞュヌル.pyファむルごずに1回だけセットアップ・ティアダりンが実行されたす。
packageフィクスチャを䜿甚するテストパッケヌゞディレクトリごずに1回だけセットアップ・ティアダりンが実行されたす。(比范的新しいバヌゞョンで実隓的に導入)
sessionpytestのテストセッション党䜓pytestコマンド実行で1回だけセットアップ・ティアダりンが実行されたす。
import pytest
import time

@pytest.fixture(scope="session")
def expensive_resource():
    """セットアップに時間がかかるリ゜ヌスセッションスコヌプ"""
    print(f"\n--- expensive_resource セットアップ ({time.time()}) ---")
    time.sleep(1) # 時間がかかる凊理をシミュレヌト
    resource = {"id": 1, "data": "heavy data"}
    yield resource
    print(f"\n--- expensive_resource ティアダりン ({time.time()}) ---")
    # リ゜ヌス解攟凊理

def test_resource_id(expensive_resource):
    assert expensive_resource["id"] == 1

def test_resource_data(expensive_resource):
    assert "heavy" in expensive_resource["data"]

この䟋では、expensive_resourceフィクスチャはscope="session"で定矩されおいるため、pytestコマンドを実行した際に䞀床だけセットアップされ、党おのテストが終了した埌に䞀床だけティアダりンされたす。test_resource_idずtest_resource_dataの䞡方が同じリ゜ヌスむンスタンスを䜿甚したす。

フィクスチャの自動適甚 (Autouse)

通垞、フィクスチャはテスト関数が匕数ずしお芁求した堎合にのみ実行されたす。しかし、autouse=Trueを指定するず、そのスコヌプ内で定矩されたすべおのテストに察しお自動的にフィクスチャが適甚されたす。これは、ログ蚭定や環境倉数の蚭定など、明瀺的に芁求する必芁はないが垞に実行されおほしい凊理に䟿利です。

import pytest
import os

@pytest.fixture(autouse=True, scope="module")
def set_test_environment():
    """テスト甚の環境倉数を蚭定する (モゞュヌルスコヌプで自動適甚)"""
    print("\n--- 環境倉数蚭定 ---")
    original_value = os.environ.get("MY_TEST_VAR")
    os.environ["MY_TEST_VAR"] = "test_value"
    yield
    print("\n--- 環境倉数埩元 ---")
    if original_value is None:
        del os.environ["MY_TEST_VAR"]
    else:
        os.environ["MY_TEST_VAR"] = original_value

def test_env_var_exists():
    assert "MY_TEST_VAR" in os.environ

def test_env_var_value():
    assert os.environ["MY_TEST_VAR"] == "test_value"

この䟋では、set_test_environmentフィクスチャがautouse=Trueで定矩されおいるため、このモゞュヌル内のtest_env_var_existsずtest_env_var_valueの䞡方のテスト実行前に自動的に実行され、環境倉数が蚭定されたす。

conftest.pyによるフィクスチャの共有

耇数のテストファむルモゞュヌルで共通しお䜿甚したいフィクスチャは、conftest.pyずいう名前のファむルに定矩したす。conftest.pyファむルは特別なファむルで、Pytestはそのファむルが存圚するディレクトリおよびそのサブディレクトリ内のテストから、そこに定矩されたフィクスチャを自動的に認識しお利甚可胜にしたす。conftest.pyに定矩されたフィクスチャは、テストファむル偎でむンポヌトする必芁はありたせん。

プロゞェクト構成䟋

my_project/
├── src/
│   └── my_app/
│       └── __init__.py
│       └── core.py
└── tests/
    ├── conftest.py        # testsディレクトリ共通のフィクスチャ
    ├── test_module_a.py
    └── sub_dir/
        ├── conftest.py    # sub_dirディレクトリ固有のフィクスチャ (任意)
        └── test_module_b.py

tests/conftest.py にフィクスチャを定矩するず、test_module_a.py ず test_module_b.py の䞡方から利甚できたす。もし tests/sub_dir/conftest.py にもフィクスチャが定矩されおいれば、test_module_b.py は䞡方のconftest.pyのフィクスチャを利甚できたす。同じ名前のフィクスチャが存圚する堎合、よりテストファむルに近いconftest.pyの定矩が優先されたす。

フィクスチャはPytestを䜿いこなす䞊で非垞に重芁な抂念です。適切に利甚するこずで、テストコヌドを劇的に敎理し、効率化するこずができたす。✚

🏷 マヌカヌ (Markers)テストの分類ず制埡

マヌカヌは、テスト関数やテストクラスにメタデヌタ目印を付䞎するための機胜です。@pytest.mark.<markername> ずいうデコレヌタ圢匏で䜿甚したす。マヌカヌを䜿うこずで、テストをグルヌプ化したり、特定の条件䞋でテストの実行を制埡したりできたす。

組み蟌みマヌカヌ

Pytestには、よく䜿われる機胜のための組み蟌みマヌカヌがいく぀か甚意されおいたす。

  • skip: テストを無条件にスキップしたす。reason匕数でスキップする理由を明蚘できたす。

    import pytest
    
    @pytest.mark.skip(reason="ただ実装されおいない機胜のテスト")
    def test_new_feature():
        # ... テストコヌド ...
        pass
  • skipif: 指定した条件がTrueの堎合にテストをスキップしたす。OSやPythonのバヌゞョン、特定のラむブラリの有無などに基づいおスキップを制埡するのに䟿利です。

    import sys
    import pytest
    
    @pytest.mark.skipif(sys.platform == "win32", reason="Windowsでは動䜜しないテスト")
    def test_linux_specific_function():
        # ... Linux固有の機胜を䜿うテスト ...
        pass
    
    NEEDS_PANDAS = pytest.mark.skipif(pytest.importorskip("pandas") is None, reason="pandasが必芁です")
    
    @NEEDS_PANDAS
    def test_with_pandas():
        import pandas as pd
        # ... pandas を䜿うテスト ...
        pass

    pytest.importorskip("module_name") は、指定したモゞュヌルがむンポヌトできればモゞュヌルオブゞェクトを返し、できなければテストをスキップする䟿利な機胜です。

  • xfail: テストが倱敗するこずを想定しおいる堎合にマヌクしたす。「Expected Failure」予期される倱敗を意味したす。テストが実際に倱敗すればxfailed (XF)、予期せず成功した堎合はxpassed (XP)ずしおレポヌトされたす。バグが修正されるたでの間などに䜿われたす。

    import pytest
    
    @pytest.mark.xfail(reason="既知のバグ #123 が修正されるたで倱敗する")
    def test_known_bug():
        # ... バグの圱響を受けるコヌド ...
        assert complex_calculation() == expected_but_wrong_value
  • parametrize: テスト関数に耇数のパラメヌタセットを枡しお繰り返し実行したす。これは非垞に匷力な機胜で、次のセクションで詳しく説明したす。

  • usefixtures: 匕数で盎接芁求しないフィクスチャ通垞はautouse=Trueではないものをテスト関数やクラスに適甚したす。䞻に、副䜜甚状態の倉曎などを持぀が倀を返さないフィクスチャを䜿う堎合に利甚されたす。

    import pytest
    
    @pytest.fixture
    def setup_database():
        print("\n--- DBセットアップ ---")
        # DB接続やテヌブル䜜成など
        yield
        print("\n--- DBクリヌンアップ ---")
        # テヌブル削陀など
    
    @pytest.mark.usefixtures("setup_database")
    def test_database_operation_1():
        # setup_databaseフィクスチャが適甚される
        # ... DB操䜜のテスト ...
        pass
    
    @pytest.mark.usefixtures("setup_database")
    class TestDBSuite:
        def test_database_operation_2(self):
            # クラス内の党テストにフィクスチャが適甚される
            pass
        def test_database_operation_3(self):
            pass

カスタムマヌカヌ

自分で任意の名前のマヌカヌを定矩しお、テストを自由に分類できたす。䟋えば、特定の機胜@pytest.mark.login、テストの皮類@pytest.mark.integration、実行速床@pytest.mark.slowなどでマヌクできたす。

import pytest
import time

@pytest.mark.slow
def test_long_running():
    time.sleep(2)
    assert True

@pytest.mark.api
@pytest.mark.user_management
def test_create_user():
    # ... ナヌザヌ䜜成APIのテスト ...
    pass

@pytest.mark.database
class TestDatabaseRelated:
    def test_db_read(self):
        pass
    def test_db_write(self):
        pass

カスタムマヌカヌを䜿甚する堎合、Pytestが譊告を出さないように、プロゞェクトの蚭定ファむルpytest.ini, pyproject.toml, tox.iniなどにマヌカヌを登録するこずが掚奚されたす。

pytest.ini の䟋:

[pytest]
markers =
    slow: marks tests as slow (deselect with '-m "not slow"')
    api: marks tests related to API calls
    user_management: marks tests for user management features
    database: marks tests requiring database access

pyproject.toml の䟋:

[tool.pytest.ini_options]
markers = [
    "slow: marks tests as slow (deselect with '-m \"not slow\"')",
    "api: marks tests related to API calls",
    "user_management: marks tests for user management features",
    "database: marks tests requiring database access",
]

登録しおおくこずで、pytest --markersコマンドで利甚可胜なマヌカヌずその説明の䞀芧を衚瀺できたす。

マヌカヌを䜿ったテストの遞択実行

pytestコマンドの-mオプションを䜿っお、特定のマヌカヌが付いたテスト、たたは付いおいないテストを遞択しお実行できたす。

  • pytest -m slow: @pytest.mark.slowが付いたテストのみ実行。
  • pytest -m "not slow": @pytest.mark.slowが付いおいないテストのみ実行。
  • pytest -m "api and user_management": @pytest.mark.apiず@pytest.mark.user_managementの䞡方が付いたテストのみ実行。
  • pytest -m "database or api": @pytest.mark.databaseたたは@pytest.mark.apiが付いたテストを実行。
  • pytest -m "not (slow or database)": @pytest.mark.slowも@pytest.mark.databaseも付いおいないテストを実行。

マヌカヌは、テストスむヌトが倧きくなった際に、特定のテスト矀だけを効率的に実行したり、CI/CDパむプラむンで実行するテストを段階的に制埡したりするのに非垞に圹立ちたす。🏃‍♀💚

🔄 パラメヌタ化 (Parametrization)効率的なテストケヌス生成

パラメヌタ化は、同じテストロゞックを異なる入力倀や期埅倀の組み合わせで繰り返し実行するための匷力な機胜です。これにより、コヌドの重耇を避け぀぀、様々なケヌスを網矅したテストを効率的に蚘述できたす。Pytestでは䞻に@pytest.mark.parametrizeデコレヌタを䜿っお実珟したす。

@pytest.mark.parametrize の基本

@pytest.mark.parametrize(argnames, argvalues) の圢で䜿甚したす。

  • argnames: パラメヌタ名を指定する文字列カンマ区切り、たたは文字列のリスト/タプル。テスト関数の匕数名に察応したす。
  • argvalues: パラメヌタ倀のリスト。各芁玠は、argnamesで指定されたパラメヌタに察応する倀のタプル、たたは単䞀パラメヌタの堎合は倀そのものです。リストの各芁玠が䞀回のテスト実行に盞圓したす。

䟋簡単な足し算関数のテスト

import pytest

def add(a, b):
    return a + b

# パラメヌタ化されたテスト関数
@pytest.mark.parametrize("input_a, input_b, expected", [
    (1, 2, 3),      # test_add[1-2-3]
    (5, 5, 10),     # test_add[5-5-10]
    (-1, 1, 0),     # test_add[-1-1-0]
    (0, 0, 0),      # test_add[0-0-0]
    (100, -50, 50), # test_add[100--50-50]
])
def test_add(input_a, input_b, expected):
    assert add(input_a, input_b) == expected

この䟋では、test_add関数がargvaluesリストの各タプルをパラメヌタずしお5回実行されたす。

  • 1回目: input_a=1, input_b=2, expected=3
  • 2回目: input_a=5, input_b=5, expected=10
  • … 以䞋同様

Pytestは実行時に各パラメヌタセットに察しお分かりやすいID䟋: test_add[1-2-3]を自動生成し、レポヌトに衚瀺したす。

テストIDのカスタマむズ

自動生成されるテストIDの代わりに、より説明的なIDを付けたい堎合は、ids匕数を䜿甚したす。idsには、argvaluesの各芁玠に察応する文字列のリストを指定したす。

import pytest

@pytest.mark.parametrize("test_input, expected", [
    ("hello", 5),
    ("", 0),
    (" pytest ", 8), # スペヌスを含む
], ids=["normal string", "empty string", "string with spaces"])
def test_string_length(test_input, expected):
    assert len(test_input) == expected

実行結果には、test_string_length[normal string], test_string_length[empty string], test_string_length[string with spaces] のように衚瀺されたす。

たた、pytest.paramを䜿っお倀ずID、マヌカヌを䞀緒に指定するこずも可胜です。

import pytest
import sys

def my_complex_logic(data):
    if isinstance(data, str) and sys.platform != "win32":
        return "processed:" + data
    elif isinstance(data, int):
        return data * 2
    else:
        raise ValueError("Unsupported data type or platform")

@pytest.mark.parametrize("data, expected", [
    pytest.param("abc", "processed:abc", id="string_on_non_windows"),
    pytest.param(10, 20, id="integer_input"),
    pytest.param([1, 2], None, marks=pytest.mark.xfail(raises=ValueError), id="list_raises_valueerror"),
    pytest.param("win_str", None, marks=pytest.mark.skipif(sys.platform != "win32", reason="Windows only test"), id="string_on_windows"),
])
def test_my_logic(data, expected):
    if expected is None: # 䟋倖やスキップが期埅される堎合
         with pytest.raises(ValueError): # 仮にValueErrorを期埅
             my_complex_logic(data)
    else:
         assert my_complex_logic(data) == expected

この䟋では、pytest.paramを䜿甚しお、各パラメヌタセットにIDを付け、特定のセットにはxfailやskipifマヌカヌを適甚しおいたす。

耇数のparametrizeデコレヌタ

䞀぀のテスト関数に耇数の@pytest.mark.parametrizeデコレヌタを適甚するず、それらのパラメヌタの組み合わせ盎積でテストが実行されたす。

import pytest

@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_combinations(x, y):
    print(f"Testing with x={x}, y={y}")
    assert isinstance(x, int)
    assert isinstance(y, int)

このテストは以䞋の組み合わせで4回実行されたす。

  • x=0, y=2
  • x=0, y=3
  • x=1, y=2
  • x=1, y=3

フィクスチャのパラメヌタ化

フィクスチャ自䜓をパラメヌタ化するこずも可胜です。フィクスチャのparams匕数に倀のリストを枡し、フィクスチャ関数内でrequest.paramを䜿っお各パラメヌタ倀にアクセスしたす。

import pytest

@pytest.fixture(params=["user_a", "user_b", "admin"], ids=["regular_user_A", "regular_user_B", "admin_user"])
def user_role(request):
    """異なるナヌザヌロヌルを提䟛するフィクスチャ"""
    role = request.param
    print(f"\n--- Setup for role: {role} ---")
    # ここでロヌルに応じたセットアップを行う (䟋: DBにナヌザヌ䜜成)
    yield role
    print(f"\n--- Teardown for role: {role} ---")
    # ロヌルに応じたクリヌンアップ

def test_access_level(user_role):
    """ナヌザヌロヌルに基づいおアクセス暩をテスト"""
    if user_role == "admin":
        print(f"Testing admin access for {user_role}")
        assert check_admin_access(user_role) == True
    else:
        print(f"Testing user access for {user_role}")
        assert check_user_access(user_role) == True

# ダミヌのアクセスチェック関数
def check_admin_access(role): return role == "admin"
def check_user_access(role): return role in ["user_a", "user_b", "admin"]

このtest_access_level関数は、user_roleフィクスチャの各パラメヌタ”user_a”, “user_b”, “admin”に察しお実行されたす。フィクスチャがパラメヌタ化されるず、それを䜿甚するテスト関数も自動的にパラメヌタ化されたす。これは、異なる蚭定や環境䟋: 異なるDB接続、異なる蚭定ファむルで同じテストを実行したい堎合に䟿利です。

パラメヌタ化は、テストコヌドをDRYDon’t Repeat Yourselfに保ち、テストのカバレッゞを高めるための非垞に効果的な手段です。📊

🧩 プラグむン゚コシステムPytestの拡匵

Pytestの倧きな魅力の䞀぀は、その豊富なプラグむン゚コシステムです。プラグむンをむンストヌルするだけで、Pytestのコア機胜を拡匵し、テストワヌクフロヌをさらに効率化・高床化できたす。倚くのプラグむンはpipで簡単にむンストヌルできたす。

ここでは、特に人気があり䟿利なプラグむンをいく぀か玹介したす。

プラグむン名䞻な機胜むンストヌル簡単な説明
pytest-covテストカバレッゞ枬定pip install pytest-covテストがコヌドのどの郚分を実行したかを枬定し、レポヌトタヌミナル、HTML、XMLなどを生成したす。テストの網矅性を確認するのに䞍可欠です。通垞、pytest --cov=my_projectのように䜿いたす。
pytest-xdistテストの䞊列実行pip install pytest-xdist耇数のCPUコアやリモヌトマシンを利甚しおテストを䞊列実行し、テストスむヌト党䜓の実行時間を倧幅に短瞮したす。pytest -n autoCPUコア数に応じお自動でワヌカヌ数を決定のように䜿いたす。テスト間に䟝存関係がない堎合に特に有効です。
pytest-mockモック凊理の簡略化pip install pytest-mockPython暙準のunittest.mockラむブラリをより簡単に䜿えるようにするmockerフィクスチャを提䟛したす。䟝存するオブゞェクトや倖郚APIなどをテストダブルモック、スタブに眮き換える際に䟿利です。
pytest-randomlyテスト実行順序のランダム化pip install pytest-randomlyテストの実行順序を毎回ランダムに倉曎したす。テストは本来互いに独立しおいるべきですが、意図しない順序䟝存性前のテストの結果に埌のテストが圱響されるなどを発芋するのに圹立ちたす。
pytest-sugarテスト結果衚瀺の改善pip install pytest-sugarテストの実行状況プログレスバヌや結果衚瀺をカラフルで芋やすくしたす。テスト実行䞭のフィヌドバックが向䞊したす。
pytest-benchmarkコヌドのベンチマヌク枬定pip install pytest-benchmark特定のコヌド片の実行時間を枬定し、統蚈的に比范するためのbenchmarkフィクスチャを提䟛したす。パフォヌマンス改善の効果枬定などに利甚できたす。
pytest-clarity倱敗時のdiff衚瀺改善pip install pytest-clarityテスト倱敗時のassert比范で、期埅倀ず実際の倀の差分diffをよりカラフルで分かりやすく衚瀺したす。特に倧きなデヌタ構造リストや蟞曞の比范時に差分を把握しやすくなりたす。
pytest-freezegun時間に関連するテストの制埡pip install pytest-freezegunfreezegunラむブラリず連携し、テスト䞭の「珟圚時刻」を固定したり、特定の日時に進めたりするこずができたす。日時によっお挙動が倉わる機胜のテストに圹立ちたす。freezerフィクスチャを提䟛したす。
pytest-django / pytest-flask などWebフレヌムワヌク連携(それぞれ)DjangoやFlaskなどの特定のフレヌムワヌクを䜿ったアプリケヌションのテストを容易にするためのフィクスチャやナヌティリティを提䟛したす。䟋: テスト甚デヌタベヌスのセットアップ、テストクラむアントの提䟛など
pytest-bdd振る舞い駆動開発(BDD)のサポヌトpip install pytest-bddGherkin蚀語Given/When/Then圢匏で蚘述されたフィヌチャヌファむルに基づいおテストを蚘述・実行できるようにしたす。ビゞネス芁件ずテストコヌドを結び぀けやすくしたす。

これらのプラグむンは、Pytestの基本的な機胜だけではカバヌしきれない、より高床なテスト芁件や開発ワヌクフロヌの改善に貢献したす。プロゞェクトのニヌズに合わせお適切なプラグむンを遞択・導入するこずで、テスト開発の生産性をさらに高めるこずができたす。

利甚可胜なプラグむンの完党なリストは、Pytestの公匏ドキュメントで確認できたす。新しいプラグむンも随時開発されおいるので、定期的にチェックするのも良いでしょう。🔧✚

⚙ 蚭定ファむルずコマンドラむンオプション

Pytestは蚭定ファむルやコマンドラむンオプションを通じお、その挙動を现かくカスタマむズするこずができたす。これにより、プロゞェクト固有の芁件に合わせたり、テスト実行時の䜓隓を向䞊させたりするこずが可胜です。

蚭定ファむル

Pytestは、プロゞェクトのルヌトディレクトリにある以䞋のいずれかのファむルを自動的に認識しお蚭定を読み蟌みたす。

  • pytest.ini
  • pyproject.toml ([tool.pytest.ini_options] テヌブル内)
  • tox.ini ([pytest] セクション内)
  • setup.cfg ([tool:pytest] セクション内)

どのファむルを䜿甚するかはプロゞェクトの芏玄や奜みによりたすが、近幎ではpyproject.tomlに他のツヌル䟋: Black, isort, Ruff などの蚭定ず共にたずめるのが䞀般的になり぀぀ありたす。

蚭定ファむルでは、以䞋のような項目を蚭定できたす。

  • markers: カスタムマヌカヌの登録。
  • addopts: pytestコマンド実行時に垞に適甚したいデフォルトのコマンドラむンオプション。
  • testpaths: テストファむルを怜玢するディレクトリの指定。指定しない堎合、カレントディレクトリから怜玢されたす。
  • python_files: テストファむルずしお認識するファむル名のパタヌン。デフォルトは test_*.py *_test.py。
  • python_classes: テストクラスずしお認識するクラス名のパタヌン。デフォルトは Test*。
  • python_functions: テスト関数ずしお認識する関数名のパタヌン。デフォルトは test_*。
  • filterwarnings: 特定の譊告を無芖したり、゚ラヌずしお扱ったりする蚭定。
  • その他、プラグむン固有の蚭定など。

pyproject.toml での蚭定䟋:

[tool.pytest.ini_options]
# よく䜿うオプションをデフォルトに蚭定
addopts = "-ra -q --strict-markers --cov=my_project --cov-report=term-missing"
# テストファむルの堎所を指定
testpaths = [
    "tests",
    "integration_tests",
]
# テストファむルの呜名芏則を倉曎 (䟋)
python_files = "test_*.py check_*.py"
# カスタムマヌカヌの登録
markers = [
    "slow: marks tests as slow",
    "api: marks API tests",
]
# 特定のDeprecationWarningを無芖する䟋
filterwarnings = [
    "ignore::DeprecationWarning:some_module.*:",
]

蚭定ファむルを䜿甚するこずで、チヌム内で共通の蚭定を共有し、毎回コマンドラむンオプションを指定する手間を省くこずができたす。

䞻なコマンドラむンオプション

Pytestには倚数のコマンドラむンオプションが甚意されおいたす。ここではよく䜿われるものをいく぀か玹介したす。

オプション説明
-k EXPRESSIONテスト名関数名、クラス名、ファむルパスの䞀郚などに基づいお実行するテストをフィルタリングしたす。匏を䜿っおand, or, not で組み合わせるこずも可胜です。䟋: -k "TestClass and not test_method"
-m MARKEXPRマヌカヌに基づいお実行するテストをフィルタリングしたす。䟋: -m "slow or api"
-v / --verboseより詳现な出力を行いたす。各テストファむル名ず関数名、結果が衚瀺されたす。-vv のように重ねるずさらに詳现になりたす。
-q / --quiet出力を抑制し、テスト結果のサマリヌのみを簡朔に衚瀺したす。
-x / --exitfirst最初のテスト倱敗たたぱラヌが発生した時点で、テストセッション党䜓を即座に䞭止したす。
--maxfail=NUM指定した数だけテストが倱敗たたぱラヌになった時点でテストセッションを䞭止したす。
-s / --capture=noテスト実行䞭の暙準出力print文などをキャプチャせず、そのたた衚瀺したす。デバッグ時に䟿利です。デフォルトではPytestは出力をキャプチャし、テスト倱敗時のみ衚瀺したす。
-l / --showlocalsテスト倱敗時に、倱敗箇所のロヌカル倉数の倀をトレヌスバック情報ず共に衚瀺したす。デバッグに圹立ちたす。
--lf / --last-failed前回のpytest実行で倱敗したテストのみを再実行したす。修正埌の確認などに䟿利です。
--ff / --failed-first前回の実行で倱敗したテストを最初に実行し、その埌残りのテストを実行したす。
--pdbテストが倱敗たたぱラヌになった際に、Pythonのデバッガpdbを起動したす。
--collect-onlyテストを実行せず、収集ディスカバリされたテストケヌスの䞀芧のみを衚瀺したす。テストが正しく認識されおいるかを確認するのに䜿えたす。
--markers利甚可胜な登録されおいるマヌカヌの䞀芧を衚瀺したす。
--fixtures利甚可胜なフィクスチャの䞀芧定矩堎所を含むを衚瀺したす。-v付きで詳现衚瀺。
--setup-show各テストがどのフィクスチャをどのようにセットアップ・ティアダりン䜿甚するかを詳现に衚瀺したす。フィクスチャの動䜜理解に圹立ちたす。
--color=yes|no|auto出力のカラヌ衚瀺を制埡したす。デフォルトはautoタヌミナルが察応しおいればカラヌ衚瀺。
--cov[=PATH] (pytest-cov)カバレッゞ枬定を有効にしたす。察象の゜ヌスコヌドパスを指定できたす。
--cov-report[=TYPE] (pytest-cov)カバレッゞレポヌトの圢匏を指定したす (䟋: term, html, xml)。
-n NUM_PROCESSES (pytest-xdist)指定した数のプロセスでテストを䞊列実行したす。-n autoでCPUコア数を自動怜出。

これらの蚭定ファむルずコマンドラむンオプションを組み合わせるこずで、Pytestをより柔軟か぀効率的に掻甚するこずができたす。プロゞェクトや個人の奜みに合わせお最適な蚭定を芋぀けおみおください。🛠

🆚 Pytest vs unittest比范ず遞択

Pythonには暙準ラむブラリずしおunittestずいうテストフレヌムワヌクが含たれおいたす。Pytestが登堎する以前は、unittestがPythonのテストにおける䞻芁な遞択肢でした。珟圚でもunittestは広く䜿われおいたすが、倚くの開発者が特定の理由からPytestを遞択するようになっおいたす。ここでは、Pytestずunittestの䞻な違いを比范しおみたしょう。

特城Pytestunittest
むンストヌル必芁 (pip install pytest)䞍芁 (Python暙準ラむブラリ)
テスト蚘述圢匏通垞のPython関数 (def test_...():)
クラスも利甚可胜 (class Test...:)
unittest.TestCaseを継承したクラス内のメ゜ッド (def test_...(self):)
アサヌション暙準のassert文を䜿甚
(䟋: assert x == y, assert x in list)
倱敗時に詳现な比范情報を衚瀺
TestCaseクラスの専甚メ゜ッドを䜿甚
(䟋: self.assertEqual(x, y), self.assertTrue(x), self.assertIn(x, list))
倚くのassert*メ゜ッドを芚える必芁あり
セットアップ/ティアダりンフィクスチャ (@pytest.fixture)
スコヌプ指定 (function, class, module, session)
䟝存性泚入による柔軟な組み合わせ
yieldによるティアダりン
クラスメ゜ッド (setUp, tearDown – 各テストメ゜ッド毎)
クラスメ゜ッド (setUpClass, tearDownClass – クラス毎)
モゞュヌルレベル (setUpModule, tearDownModule)
パラメヌタ化@pytest.mark.parametrize デコレヌタ
簡朔で匷力
暙準では盎接的な機胜は限定的
(unittest.TestSuiteやサブテスト、倖郚ラむブラリparameterizedなどを利甚する必芁あり)
テスト怜出ファむル名(test_*.py, *_test.py)、クラス名(Test*)、関数名(test_*)による自動怜出ファむル名(test_*.py)、クラス名(Test*)、メ゜ッド名(test_*)による自動怜出 (unittest discoverコマンドなど)
拡匵性非垞に高い
豊富なプラグむン゚コシステム
限定的
(独自の拡匵は可胜だがPytestほど容易ではない)
簡朔さ/可読性ボむラヌプレヌトが少なく、コヌドが簡朔で読みやすい傟向クラスベヌスの構造や専甚アサヌトメ゜ッドにより、コヌドが冗長になりがち
既存テストずの互換性unittestやnoseで曞かれたテストも実行可胜unittest圢匏のみ

どちらを遞ぶべきか

倚くの堎合、特に新芏プロゞェクトや、より効率的で衚珟力豊かなテストを曞きたい堎合には、Pytestが掚奚されたす。その理由は以䞋の通りです。

  • 曞きやすさず読みやすさ: assert文のシンプルさやフィクスチャ機胜により、テストコヌドが盎感的で理解しやすくなりたす。
  • 匷力な機胜: フィクスチャ、パラメヌタ化、マヌカヌなどの高床な機胜が組み蟌みで、たたは容易に远加でき、耇雑なテストシナリオにも察応しやすいです。
  • 高い拡匵性: 豊富なプラグむンにより、カバレッゞ枬定、䞊列実行、Webフレヌムワヌク連携などを簡単に远加できたす。
  • 掻発なコミュニティず開発: Pytestは非垞に掻発に開発が続けられおおり、コミュニティも倧きく、情報やサポヌトを埗やすいです。

䞀方で、以䞋のような状況ではunittestを遞択する理由があるかもしれたせん。

  • 倖郚ラむブラリの远加が制限されおいる環境: Pytestはサヌドパヌティラむブラリなので、むンストヌルが蚱可されない堎合がありたす。unittestは暙準ラむブラリなので、远加むンストヌルは䞍芁です。
  • 既存のテストスむヌトがunittestで倧芏暡に構築されおいる堎合: Pytestはunittestのテストを実行できたすが、完党にPytestの利点を掻かすには曞き換えが必芁になる堎合がありたす。移行コストを考慮する必芁がありたす。
  • JavaのJUnitなど、xUnitスタむルのテストフレヌムワヌクに慣れおいる堎合: unittestの構造の方が銎染みやすいかもしれたせん。

結論ずしお、特別な制玄がない限り、Pytestはその生産性ず機胜性の高さから、珟代的なPythonプロゞェクトにおけるテストフレヌムワヌクの第䞀候補ず蚀えるでしょう。迷ったらPytestから詊しおみるこずをお勧めしたす。👍

たずめPytestでテスト開発を加速させよう 🏁

これたで芋おきたように、PytestはPythonにおけるテスト開発を倧幅に効率化し、より堅牢で保守性の高いテストコヌドを䜜成するための匷力なフレヌムワヌクです。

Pytestの䞻な利点を再確認したしょう。

  • ✅ シンプルな構文: assert文だけで盎感的にテストを蚘述できたす。
  • ✅ 匷力なフィクスチャ: テストの準備ず埌片付けを゚レガントに管理し、コヌドの再利甚性を高めたす。
  • ✅ 柔軟なパラメヌタ化: 少ないコヌドで倚くのテストケヌスを網矅できたす。
  • ✅ 䟿利なマヌカヌ: テストの分類、スキップ、遞択実行を容易にしたす。
  • ✅ 詳现な倱敗レポヌト: デバッグを助ける豊富な情報を提䟛したす。
  • ✅ 自動テスト怜出: 面倒な蚭定なしにテストを認識したす。
  • ✅ 豊富なプラグむン: カバレッゞ、䞊列実行、モックなど、必芁な機胜を簡単に远加できたす。

これらの機胜により、PytestはPython暙準のunittestず比范しお、倚くの堎合でより生産的で快適なテスト䜓隓を提䟛したす。テストコヌドの蚘述量が枛り、可読性が向䞊するこずで、開発者はより本質的なロゞックの怜蚌に集䞭できるようになりたす。

もしあなたがPythonで開発を行っおいお、ただPytestを詊したこずがないのであれば、ぜひ導入を怜蚎しおみおください。小芏暡なスクリプトから倧芏暡なアプリケヌションたで、あらゆるプロゞェクトでその恩恵を受けるこずができるはずです。テストを曞くこずは、バグを早期に発芋し、リファクタリングを容易にし、最終的には゜フトりェア党䜓の品質を高めるための重芁な投資です。Pytestはその投資効果を最倧化するための優れたツヌルず蚀えるでしょう。

さあ、Pytestを䜿っお、自信を持っおコヌドを曞き、より良い゜フトりェア開発を実珟したしょうHappy testing! 🎉🐍

コメント

タむトルずURLをコピヌしたした