見出し画像

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)


この記事が気に入ったらサポートをしてみませんか?