Pytestの使い方メモ

WEBアプリ用のエンドポイントを作成して(本当は書き直しだけど)きちんと動くか確認するのにpytestを使用してみたので、使い方をメモしておく。(使ったのだけ)

インストール

pythonの仮想環境から

pip install pytest

使い方

プロジェクトのルートディレクトリから

pytest

ファイルや関数を指定して実行

pytest tests/test_routes.py     #test_routes.pyファイルだけ実行
pytest tests/test_routes.py::api_test #test_routes.pyの中のdef api_test関数だけ
putest tests/test_routes.py::TestClass::api_test #test_routes.pyの中のTestClassの中の関数

実行オプションは色々あるけど使わなくてもOK。念の為

オプション説明
-v詳細表示(Verbose)pytest -v
-q簡易表示(Quiet)pytest -q
-k特定のキーワードにマッチするテストだけ実行pytest -k "add"
-sprint()の出力をそのまま表示pytest -s
--maxfail=1最初の失敗でテストを停止pytest --maxfail=1
--disable-warnings警告を非表示にするpytest --disable-warnings

ファイルのルール

テストファイルの命名規則

pytestは自動でテストを検出します。
テストファイルと関数はtestをつけると自動で実行される。*_test.pyでも良いみたい

  • テストファイル名test_*.py または *_test.py
    例:test_sample.py
  • テスト関数名test_で始める
    例:def test_add():

自動読み込みファイル

conftest.pyは自動で読み込まれる。import不要。

基本的には全モジュールから使用するようなfixtureを記載しておく

ただ、Scope設定がちょっと面倒(後述)

テスト用ファイルの書き方

普通にテスト用のコードを書けば良い。test_で関数名をつければ実行される。

@pytest.fixture

@pytest.fixtureをつけて関数を作成すると、テスト用関数で使用できる

@pytest.fixture(scope='session')
def client(app):
  return app.test_client()
@pytest.fixture(scope='session')
def superuser()
  return {"id": user.id, "email": "foo@exsample.com", "password": "superpass"}
def test_create_user(client)
   res = client.post("/auth/login", json={
                                          "email": superuser["email"],
                                          "password": superuser["password"]})

みたいな感じ

Scope
スコープ名作成タイミング破棄タイミング主な用途
function(デフォルト)各テスト関数の実行前テスト関数終了後テストごとに独立した状態が必要な場合(例:1テスト1トランザクション)
classクラス内の最初のテスト実行前クラス内の最後のテスト終了後クラス単位で共通のセットアップを使いたい場合
moduleモジュール内の最初のテスト実行前モジュール内の最後のテスト終了後モジュール単位でDBや外部サービス接続を共有したい場合
package(pytest 7.0以降)パッケージ内で最初のテスト実行前パッケージ内の最後のテスト終了後パッケージ全体で一度だけ初期化するリソース
sessionテスト全体開始時に1回だけテスト全体終了時テスト全体で一度だけ必要なリソース(例:DB接続、テスト用サーバ起動)

広いScopeの関数から狭いScopeのfixtureは使用できない(mismatchエラーが出る。)

import pytest

@pytest.fixture(scope="function")
def function_resource():
  print("functionスコープ生成")
  return "function_resource"

@pytest.fixture(scope="session")
def session_needs_function(function_resource):
  # ❌ 広いスコープで狭いスコープを依存するとエラー
  return f"session_using_{function_resource}"

def test_use(session_needs_function):
  assert "function_resource" in session_needs_function

最初によく考えて作成しないと、ハマる。
きれいなコードを考えなければよいが、データベース系だと、データベースに登録するのは基本的にsessionにしないと2重登録エラーになる。ただし、ログイン関連とかはfunctionにしたいので、
ユーザーデータ、ユーザーデータのデータベース登録、ログインなど、別々にしたほうが無難。

@pytest.mark.parametrize

いろいろなパラメータでテストしたい時使うと便利。

@pytest.mark.parametrize("num", [1, 2, 3])
def test_is_positive(num):
assert num > 0

上記は、test_is_positive(num):がnumの値を変更して3回実行される。
import pytest

@pytest.mark.parametrize("a, b", [(1, 2), (3, 5), (-1, 4)])
def test_multiply(a, b):
 result = a * b
 print(f"テスト中: a={a}, b={b}, result={result}")
 assert result == a * b # 単純な確認

パラメータは、2個以上でも設定可能

他にもデコレータは色々あるけど、上記の2個ぐらいしか使わない

結果表示の味方

結果の表示はなんとなくわかるので、割愛します。
関数の引数に関しては、内容が表示されるので、確認すると間違えがわかりやすい
print分を入れておくと、下に表示される。

tests/test_new_user.py F [100%]
=================================== FAILURES ===================================
_____________ test_full_user_creation_flow _____________
superuser_login = >
superuser = {‘email’: ‘superadmin@example.com’, ‘id’: 1, ‘password’: ‘superpass’}

def test_full_user_creation_flow(superuser_login, superuser):
# ① Company を作成
company_payload = {‘name’: ‘TestCompanyX’}
company_res = superuser_login.post(‘/companies’, json=company_payload)
assert company_res.status_code == 200
E assert 201 == 200
E + where 201 =.status_code
tests/test_new_user.py:5: AssertionError
=========================== short test summary info ============================
FAILED tests/test_new_user.py::test_full_user_creation_flow – assert 201 == 200
============================== 1 failed in 2.46s ===============================


タイトルとURLをコピーしました