FastAPIによるHello Worldとドキュメント生成、テストの実行

# はじめに
みずほリサーチ＆テクノロジーズ株式会社の @fujine です。本記事は[FastAPI学習コンテンツシリーズ](https://qiita.com/fujine/items/a6b13dd56a233309f335)の1つとして、

- "Hello World"を出力する簡単なAPIプログラムの作成
- Swaggerドキュメントの生成
- テストコードの実装と実行

までを体験することを目的とします。

# 0. 仮想環境の作成と有効化
`hello`という仮想環境を作成して有効化します。
```txt
python -m venv hello

cd hello
Scripts\activate
```

# 1. FastAPIと関連パッケージのインストール

FastAPIとpytest（テストフレームワーク）をインストールします。

```sh
pip install fastapi[standard] pytest
```

# 2. "Hello World"を出力するAPIの作成

`main.py` というファイルを作成し、以下のコードを記述します。

```python:hello/main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello World"}
```

プログラムの内容を解説します。

- `app = FastAPI()`で、FastAPIのインスタンスを作成します。この`app`オブジェクトが、**APIの全てのエンドポイントや設定を管理**する中心的な存在となります。`app`という名前は慣例であり、変更可能です。
- `@app.get("/")`は**デコレータ**です。ここでは、`root`関数を`GET`メソッドのルートパス(`/`)に紐付けます。つまり、ブラウザで`http://127.0.0.1:8000/` にアクセスすると、`root`関数が実行されます。 GET以外にも、`@app.post("/")`、 `@app.put("/")`、 `@app.delete("/")` など、他のHTTPメソッドにも対応するデコレータがあります。
- `async def root():`で`root`関数を定義しています。`async`キーワードは、この関数が**非同期関数**であることを示します。非同期関数を使用することで複数のリクエストを効率的に処理でき、特にI/Oバウンドな処理（データベースアクセス、外部API呼び出しなど）で効果を発揮します。
 
:::note
非同期処理に詳しくない場合は、ひとまず「高速化のための機能」と考えておけば問題ありません。単純なAPIであれば、`async`を省略して`def root():`と実装しても動作します。
:::

- `return {"message": "Hello World"}`でAPIのレスポンスを定義します。辞書を返すと、FastAPIは自動的にJSON形式に変換してクライアントに返送します。


# 3. プログラムの実行

コマンドプロンプトで以下のコマンドを実行し、Uvicornを起動します。

```sh
uvicorn main:app --reload
```

`--reload` オプションを付けると、コードの変更が自動的に反映されます。

ブラウザで `http://127.0.0.1:8000/` にアクセスすると、`{"message": "Hello World"}` がJSON形式で表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2969456/96d5c431-cc10-fc46-e0e1-20524f98f66f.png)

コマンドプロンプトで`curl`コマンドを実行する方法でも同じ結果が得られます。

```sh
curl http://127.0.0.1:8000
{"message":"Hello World"}
```

# 4. Swaggerドキュメントの確認

FastAPIはインタラクティブな**Swagger APIドキュメント**を自動生成することができます。

Uvicornを起動した状態で、ブラウザで `http://127.0.0.1:8000/docs`にアクセスします。すると`Swagger UI`が表示され、APIのエンドポイント `/` の詳細を確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2969456/868226c5-3213-9d9f-48f5-8ab6645f584d.png)

「Try it out」ボタンをクリックして、APIを実際にテストすることも可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2969456/84aa796f-d1b7-daa1-d5d1-454403c5919b.png)


`http://127.0.0.1:8000/redoc`でRedoc形式のドキュメントも確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2969456/bdb071f5-a7f6-1d05-f524-be5e82d1fbb9.png)


# 5. 単体テストの作成
プログラムが正しく動作することを確認するために、テストコードを作成します。
`test_main.py`というファイルを作成し、以下コードを記述します。

```python:hello/test_main.py
from fastapi.testclient import TestClient

from main import app


client=TestClient(app)

def test_read_main():
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Hello World"}
```

プログラムの内容を解説します。

- `from main import app`で、作成したFastAPIアプリケーションの`app`インスタンスをインポートします。
- `client=TestClient(app)`では、`app`を渡して`TestClient`インスタンスを作成します。これにより、`client`オブジェクトを通じてFastAPIアプリケーションとやり取りできるようになります。`TestClient`は、FastAPIアプリケーションをテストするための特別なクライアントです。
- `def test_read_main():`がテスト関数です。関数名を`test_` で始めることで、`pytest`が自動的にテスト関数を探索して実行できるようになります。

このテスト関数では、`client.get("/")`でエンドポイントにアクセスし、
- レスポンスのステータスコードが`200`（正常終了）であること
- レスポンスの中身が`{"message": "Hello World"}`と一致すること

の2点を検証します。

# 6. テストの実行

以下のコマンドでテストを実行します。

```sh
pytest test_main.py
```

テストが成功すると、以下のような出力が表示されます。`test_main.py .`と出力された行は、`test_main.py`に実装された1件のテスト関数が正常終了したことを意味します。

```txt
====================== test session starts ======================
platform win32 -- Python 3.13.0, pytest-8.3.4, pluggy-1.5.0
...
collected 1 item

test_main.py .                                             [100%]

======================= 1 passed in 5.26s =======================
```

:::note warn
もしテストが失敗した場合、`.`ではなく`F`が出力され、期待値と実際の値の違いがコンソールに出力されます。
:::

---

以上で本記事は終了です。

次回の記事では、HTTPメソッドとパスパラメータの実装について解説いたします。