はじめに
🦁テストコード書いてないのか? nikkieです。
FastAPIとは付き合いがそこそこありますが、ここ最近TestClient
を使ったテストを初めて書く機会が訪れました。
目次
ユーザガイド「Testing」
これを見ればOKそうなドキュメントがありました(ありがたや〜)。
こちらに沿って手を動かしていきます。
今回のゴールはHello Worldするエンドポイントのテストとします(初めてなのでハードルは低く!)。
アプリケーションのヘルスチェックのテストになるかなと考えています。
app.py
from fastapi import FastAPI app = FastAPI() @app.get("/") def hello(): return {"message": "Hello World!"}
動作環境
- Python 3.12.6
python -m pip install 'fastapi[standard]' pytest
- fastapi 0.115.5
- pytest 8.3.3
詳細なバージョン
annotated-types==0.7.0 anyio==4.6.2.post1 certifi==2024.8.30 click==8.1.7 dnspython==2.7.0 email_validator==2.2.0 fastapi==0.115.5 fastapi-cli==0.0.5 h11==0.14.0 httpcore==1.0.7 httptools==0.6.4 httpx==0.27.2 idna==3.10 iniconfig==2.0.0 Jinja2==3.1.4 markdown-it-py==3.0.0 MarkupSafe==3.0.2 mdurl==0.1.2 packaging==24.2 pluggy==1.5.0 pydantic==2.10.2 pydantic_core==2.27.1 Pygments==2.18.0 pytest==8.3.3 python-dotenv==1.0.1 python-multipart==0.0.17 PyYAML==6.0.2 rich==13.9.4 shellingham==1.5.4 sniffio==1.3.1 starlette==0.41.3 typer==0.13.1 typing_extensions==4.12.2 uvicorn==0.32.1 uvloop==0.21.0 watchfiles==1.0.0 websockets==14.1
Testingのドキュメントによるとhttpxが必要ですが、pip install fastapi[standard]
でインストールされていました。
はじめてTestClient
. ├── app.py └── test_app.py
test_app.py
from fastapi.testclient import TestClient from app import app client = TestClient(app) def test_ヘルスチェック(): response = client.get("/") assert response.status_code == 200 assert response.json() == {"message": "Hello World!"}
「そう書くのか〜!」となったポイントは、app.py
にあるapp
をimportした上でTestClient
を初期化した1点。
あとはTestClient
インスタンスをHTTPXのように使います!
Use the TestClient object the same way as you do with httpx.
GET /
のレスポンスのステータスコードとJSONの内容をpytestの書き方で検証しました。
% pytest -v ============================= test session starts ============================== platform darwin -- Python 3.12.6, pytest-8.3.3, pluggy-1.5.0 -- /.../.venv/bin/python collected 1 item test_app.py::test_ヘルスチェック PASSED [100%] ============================== 1 passed in 0.33s ===============================
Bigger Applicationのファイル配置でテスト
Testingのドキュメントの中の「Separating tests」より。
アプリケーションのパッケージにテストコードも配置するんですね!
. └── app/ ├── __init__.py ├── main.py └── test_main.py
app/__init__.py
でapp
をパッケージ化app/main.py
でアプリケーションを実装app/test_main.py
がテストコード
「Bigger Applications - Multiple Files」のドキュメントには
app
のサブパッケージとして、routers
やinternal
が導入されています。
上記のヘルスチェックのコードをapp
パッケージに移動してみたところ、app
のimportを相対importに変えるだけ2でテストは通り続けました。
from fastapi.testclient import TestClient -from app import app +from .main import app client = TestClient(app) def test_ヘルスチェック():
% pytest -v ============================= test session starts ============================== platform darwin -- Python 3.12.6, pytest-8.3.3, pluggy-1.5.0 -- /.../.venv/bin/python collected 1 item app/test_main.py::test_ヘルスチェック PASSED [100%] ============================== 1 passed in 0.29s ===============================
ちなみにpytest
コマンドでapp/test_app.py
の中のテストが見つかるのは、
- (testpaths未設定なので)現在のディレクトリからテストの収集を始める
- ディレクトリの中を再帰的に
test_*.py
または*_test.py
という名前のファイルを探す- 👉
app/test_app.py
が見つかりますね
- 👉
test
で名前が始まる関数やメソッドをテストアイテムとして収集- 👉
test_ヘルスチェック
!
- 👉
という仕組みによると理解しています。
ref: https://docs.pytest.org/en/stable/explanation/goodpractices.html#conventions-for-python-test-discovery
終わりに
FastAPI製アプリのテストコードを初めて書きました。
- アプリケーションをimportして
TestClient
に渡して初期化 TestClient
はHTTPXだと思って使えばよい- テストコードはアプリケーションの中に配置される(通常のプロジェクトとの違いだな〜)
手元のアプリケーションはまだBiggerではありませんが、テストコードを書きつつ育てていけたらと思っています。
- Testingのドキュメントより「Create a TestClient by passing your FastAPI application to it.」↩
- Testingのドキュメントのここ:https://fastapi.tiangolo.com/tutorial/testing/#testing-file↩