pytestの基本的な使い方

2022-12-31
2022-12-31

2022 年最後の技術記事は pytest についてです。

Python を業務で使っていると、テストを書くことは必ずあると思います。

そんな時にまず候補に上がるのが pytest。

今回はそんな pytest の基礎的なところを書いていきます。

pytest を使う準備

まずはインストールします。例によって poetry で環境を作るのでpoetry add pytestします。

以下は今回のテスト用のディレクトリ構成です。

.
├── poetry.lock
├── pyproject.toml
├── src
│   ├── __init__.py
│   ├── error.py
│   ├── module1.py
│   └── sandbox.ipynb
└── tests
    ├── __init__.py
    └── test_module.py

pytest の設定ファイル

pytest を実行する際に必要に応じて使いたい設定を書いておきます。基本的な使い方のみならなくても動作します。

pytest では Python でよくある設定ファイル複数に対応しています。poetry を使っているならばpyproject.tomlに書くのが良いでしょう。単純に pytest を使いたいだけならpytest.iniに書きます。

(参考:https://docs.pytest.org/en/latest/reference/customize.html)

よく使う設定だけ書いていきます。出来ることはたくさんあるので、公式サイトのCommand-line Flagsを参照ください。 また別途プラグインのインストールが必要ですが、よく使うのでカバレッジを計測するpytest-covのフラグも載せておきます。

フラグ説明
-sprint 文 などを表示できるようになる
-vテストケース名、テストケースごとに PASSED や FAILED などの詳細情報が表示される
-xテストが失敗した時点でテストを終了する
-m <maker名>指定した maker 名 の デコレータをつけたテストのみ実行される。デコレータは次のように書く@pytest.mark.<maker名>
--durations=Nテストケースごとの実行時間、setup や teardown の時間を表示する。N=0 だと全てのテストケースに対して実行される
--cov --cov-branch条件分岐のカバレッジ(C1)をみるためのフラグ

これらを書いたpyproject.tomlは以下になります。duration-xは適宜使うので設定ファイル上は一旦除いてます。

[tool.pytest.ini_options]
addopts = "--cov --cov-branch -s -v"
testpaths = [
    "tests",
]

pytest で正常系をテストする場合

簡単な以下の関数をテストしてみます。

from src.error import NotIntError
def add_one(x: int) -> int:
    if not isinstance(x, int):
        raise NotIntError("int以外の型です。入力はintのみです")
    return x + 1

後々異常系もテストしたいので、あえて例外を発生させてます。

そしてこれが正常系のテストコード。

from src.module1 import add_one
def test_正常系():
    result = add_one(1)
    expect = 2
    assert result == expect

実行はターミナルでpytestと打つだけでいけます。ファイルやクラスを指定して実行することもできます。

(.venv) MacBook-Pro pytest-check % pytest
====================================================================================================== test session starts ======================================================================================================
platform darwin -- Python 3.8.7, pytest-7.2.0, pluggy-1.0.0
rootdir: /Users/work/pytest-check
collected 1 item

tests/test_module.py .                                                                                                                                                                                                    [100%]

======================================================================================================= 1 passed in 0.01s =======================================================================================================

これで正常系のテストができました。

pytest で異常系をテストする場合

続いて異常系です。以下が異常系テストです。

def test_異常系():
    with pytest.raises(NotIntError):
        _ = add_one("1") # strの数値を渡す

これで先ほど関数で定義しておいたNotIntErrorが発生するので、pytest.raises で発生する例外を指定します。

pytest で複数パラメータで実施する

正常系のテストをかなり適当に書いていたのでもう少し整えてみます。 ここではまず複数のパラメータを試すようにします。

@pytest.mark.parametrize("val, expect", [(1, 2), (100, 101), (-1, 0)])
def test_正常系(val: int, expect: int):
    result = add_one(val)
    assert result == expect

パラメータを複数テストに渡すには@pytest.mark.parametrizeのデコレータを使います。 デコレータに渡すのは引数名と引数のペアのリストです。

pytest の fixture(フィクスチャー)

テストの事前準備と後処理(いわゆる setup, teardown)を実行してくれるのが fixture です。

よくデータベースに保存されたデータを前提とした処理などに使われます。

今回はもっと簡単にprintでやってみます。

@pytest.fixture()
def start_print():
    print("Start Test !!")
@pytest.mark.parametrize("val, expect", [(1, 2), (100, 101), (-1, 0)])
def test_正常系(start_print, val: int, expect: int):
    result = add_one(val)
    assert result == expect

実行すると以下のようになります。(見づらいので-vは使わずに実行してます)

tests/test_module.py Start Test !!
.Start Test !!
.Start Test !!
..

たしかに print 文の内容が表示されています。 続いて teardown の処理を書いてみます。先ほどの fixture に ß 以下のように yield 句とその後に print 文を追加します。

@pytest.fixture()
def start_print():
    print("Start Test !!")
    yield
    print("Finish Test")

以下が実行結果です。

tests/test_module.py Start Test !!
.Finish Test
Start Test !!
.Finish Test
Start Test !!
.Finish Test
.

テストケースの最後に yield 句の後の print 文が実行されていることがわかります。

まとめと次回に向けたメモ

今回は pytest の超基本的なところだけを記載しました。 実はまだまだ書きたいことがあるので、次回続きを書きたいと思います。

書くことメモ

  • パラメータの複数組み合わせ方法
  • fixtute から fixture を使う方法
  • fixture にパラメータを渡す方法
  • モックの使い方