メインコンテンツへスキップ
見出し画像

pytestの使い方

    「pytest」の使い方をまとめました。

    1. pytest

    「pytest」はPythonスクリプトをテストするためのフレームワークです。

    2. インストール

    Python 3.7以降で、以下のコマンドでインストールします。

    $ pip install -U pytest pytest-mock pytest-freezegun

    3. はじめてのテストの実行

    はじめてのテストの実行の手順は、次のとおりです。

    (1) Pythonスクリプト「test_sample.py」を作成し、以下のように編集。

    ・test_sample.py

    def func(x):
        return x + 1
    
    def test_answer():
        assert func(3) == 5

    (2) テストを実行。

    $ pytest -q
    F                                                                                                                                                           [100%]
    ============================================================================ FAILURES =============================================================================
    ___________________________________________________________________________ test_answer ___________________________________________________________________________
    
        def test_answer():
    >       assert func(3) == 5
    E       assert 4 == 5
    E        +  where 4 = func(3)
    
    test_sample.py:6: AssertionError
    ===================================================================== short test summary info =====================================================================
    FAILED test_sample.py::test_answer - assert 4 == 5
    1 failed in 0.03s

    今回は、func(3)が5を返さなかったため、FAILEDが報告されています。

    ◎ テストの実行コマンド
    テストの実行コマンドは、次のとおりです。-qで出力を簡潔にしています。

    $ pytest -q
    $ pytest -q <ファイル名>
    $ pytest -q <ディレクトリ名>

    ◎ テストの実行対象
    テストの実行対象となるスクリプト名は「test_*.py」「*_test.py」です。

    test_sample.py

    テストの実行対象となるメソッド名は「test_*()」です。

    def test_answer():
        〜テスト〜

    テストの実行対象となるクラス名は「Test*」です。

    class TestClass:
        def test_one(self):
            〜テスト〜
    
        def test_two(self):
            〜テスト〜

    4. assert

    「assert」は、特定の条件を満たすかどうかを検証します。

    assert <条件>

    使用例は、次のとおりです。
    ・test_assert.py

    def func(x):
        return x + 1
    
    def test_answer():
        assert func(3) == 5

    5. raises

    「raises」は、特定の例外が発生したかどうかを検証します。

    with pytest.raises(<例外>):
        <処理>

    使用例は、次のとおりです。
    ・test_raises.py

    import pytest
    
    def f():
        raise SystemExit(1)
    
    def test_mytest():
        with pytest.raises(SystemExit):
            f()

    6. mark

    「mark」は、テストにメタデータを付与することができます。

    ◎ テストのスキップ
    @pytest.mark.skip
    を付与することで、テストをスキップできます。

    ・test_mark1.py

    import pytest
    
    def func(x):
        return x + 1
    
    @pytest.mark.skip
    def test_answer():
        assert func(3) == 5

    ◎ テストのパラメータに値を提供
    @pytest.mark.parametrize
    を付与することで、テストのパラメータに値を提供できます。これによって、様々な値セットでテストできます。

    ・test_mark2.py

    import pytest
    
    @pytest.mark.parametrize("x,y,sum", [
        (1, 2, 3),
        (4, 5, 6),
    ])
    def test_add(x, y, sum):
        assert (x + y) == sum

    7. fixture

    「fixture」を使うことで、テストのパラメータにオブジェクトを提供したり、テストに前処理や後処理を追加したりできます。

    ◎ テストのパラメータにオブジェクトを提供
    関数に@pytest.fixtureを付与することで、テストのパラメータで提供するオブジェクト「fixture」として定義できます。テストでこの関数と同じ名前のパラメータを使用することで、fixtureを提供できます。

    使用例は、次のとおりです。

    ・test_fixture1.py

    import pytest
    
    class MyDatabase():
        def open(self):
            print("open")
    
        def update(self):
            print("update")
    
        def close(self):
            print("close")
    
    @pytest.fixture()
    def my_database():
        return MyDatabase()
    
    def test_sample(my_database):
        my_database.open()
        my_database.update()
        my_database.close()

    pytestでprint()を出力するには、-sを指定します。

    $ pytest -q -s test_fixture1.py
    open
    update
    close
    .
    1 passed in 0.00s

    ◎ fixtureの関数に前処理と後処理を追加
    fixtureの関数内で前処理と後処理を追加することもできます。

    ・test_fixture2.py

    import pytest
    
    class MyDatabase():
        def open(self):
            print("open")
    
        def update(self):
            print("update")
    
        def close(self):
            print("close")
    
    @pytest.fixture()
    def my_database():
        # 前処理
        my_database = MyDatabase()
        my_database.open()
    
        # テスト
        yield my_database
    
        # 後処理
        my_database.close()
    
    def test_sample(my_database):
        my_database.update()
    $ pytest -q -s test_fixture2.py
    open
    update
    .close
    1 passed in 0.01s

    ◎ クラスのテストに前処理を追加
    @pytest.mark.usefixtures
    でクラスのテストに前処理を追加できます。

    ・test_fixture3.py

    import pytest
    
    @pytest.fixture()
    def sample_fixture():
        print("sample_fixture")
    
    @pytest.mark.usefixtures('sample_fixture')
    class TestSample(object):
        def test_sample1(self):
            print("test_sample1")
    
        def test_sample2(self):
            print("test_sample2")
    
    $ pytest -q -s test_fixture3.py
    sample_fixture
    test_sample1
    .sample_fixture
    test_sample2

    ◎ グローバルで使うfixtureの定義
    グローバルで使うfixtureを定義は、conftest.pyに記述します。

    ・conftest.py

    import pytest
    
    @pytest.fixture()
    def sample_fixture():
        print("sample_fixture")

    ・test_fixture4.py

    import pytest
    
    @pytest.mark.usefixtures('sample_fixture')
    class TestSample(object):
        def test_sample1(self):
            print("test_sample1")
    
        def test_sample2(self):
            print("test_sample2")
    $ pytest -q -s test_fixture4.py
    sample_fixture
    test_sample1
    .sample_fixture
    test_sample2

    8. Mock

    Mockは、ユニットテストを円滑に進めるため、関数やオブジェクトを代理させるのモジュールになります。

    テストのパラメータにmockerを記述し、mocker.patch("<関数名>", return_value=<戻り値>)を呼ぶことで、任意の関数の戻り値変更できます。

    ・sample.py (テスト対象)

    import requests
    
    def sample1(url):
        return check_url(url)
    
    def check_url(url):
        try:
            response = requests.get(url)
            return response.status_code
        except Exception:
            return 0
    

    ・test_mock.py (テスト)

    from sample import sample1
    
    def test_sample1():
        assert sample1("https://www.google.com/") == 200
    
    def test_sample2(mocker):
        mocker.patch("sample.check_url", return_value=404)
        assert sample1("https://www.google.com/") == 404

    9. freezegun

    datetime.now()をMock化したい場合も多いと思いますが、組み込み関数のためmockerではMock化できません。freezegunを使うことで可能になります。

    @pytest.mark.freeze_time()でdatetime.now()をMock化します。

    import datetime
    import pytest
    
    @pytest.mark.freeze_time(datetime.datetime(2022, 1, 1, 0, 0))
    def test_datetime_now():
        now = datetime.datetime.now()
        assert now == datetime.datetime(2022, 1, 1, 0, 0)


     
     

    npaka

     
     
    プログラマー。iPhone / Android / Unity / ROS / AI / AR / VR / RasPi / ロボット / ガジェット。年2冊ペースで技術書を執筆。アニソン / カラオケ / ギター / 猫 twitter : @npaka123

    あなたへのおすすめ