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" |
-s | print()の出力をそのまま表示 | 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 ===============================

