Python | テスト・設計・品質:E2E テスト

Python Python
スポンサーリンク

E2E テストって何?まずはざっくりイメージから

E2E テスト(End-to-End テスト)は、
「ユーザーの操作の始まりから終わりまで、システム全体を通して動かして確認するテスト」です。

単体テスト
関数レベルのテスト(超局所)

結合テスト
モジュール同士・DB とのつながりのテスト(アプリ内部の流れ)

E2E テスト
ブラウザや HTTP クライアントから「本物と同じように」叩いて、
画面や API の振る舞いを“丸ごと”確認するテスト

というイメージです。

ユーザー視点で
「この操作をしたら、ちゃんとこういう結果になるか?」
を確認するのが E2E テストの役割です。


例題シナリオ:シンプルな Web アプリのログイン

想定するアプリ

Python(例えば FastAPI)で、こんな API があるとします。

POST /login
メールアドレスとパスワードを受け取る
正しければトークンを返す
間違っていれば 401 を返す

E2E テストでは、「本物のサーバーを立ち上げて、HTTP 経由でログインを試す」ことをします。

ここでは、FastAPI を例にしますが、考え方は Flask などでも同じです。


コード例:FastAPI の簡単なログイン API

アプリ本体(超シンプル版)

# app.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

class LoginRequest(BaseModel):
    email: str
    password: str

VALID_USER = {
    "email": "taro@example.com",
    "password": "secret",
    "token": "TOKEN-123",
}

@app.post("/login")
def login(req: LoginRequest):
    if req.email == VALID_USER["email"] and req.password == VALID_USER["password"]:
        return {"token": VALID_USER["token"]}
    raise HTTPException(status_code=401, detail="Invalid credentials")
Python

これはあくまで「説明用の超簡易版」です。
実際には DB や認証基盤とつながることになりますが、
E2E テストの考え方は同じです。


E2E テストの書き方(HTTP レベルで叩く)

テスト用クライアントを使う(FastAPI の場合)

FastAPI には、テスト用の HTTP クライアントが用意されています。
これを使うと、「サーバーを別プロセスで立てなくても、HTTP っぽく叩ける」ので、
E2E にかなり近いテストが書けます。

# test_e2e_login.py

from fastapi.testclient import TestClient
from app import app

client = TestClient(app)

def test_login_success():
    resp = client.post(
        "/login",
        json={"email": "taro@example.com", "password": "secret"},
    )
    assert resp.status_code == 200
    data = resp.json()
    assert data["token"] == "TOKEN-123"

def test_login_failure():
    resp = client.post(
        "/login",
        json={"email": "taro@example.com", "password": "wrong"},
    )
    assert resp.status_code == 401
Python

ここでやっていることは、まさに「End-to-End」です。

HTTP リクエストを送る
アプリ内部でルーティング・バリデーション・ロジックが動く
HTTP レスポンスとして返ってくる
ステータスコードや JSON の中身を確認する

単体テストや結合テストでは、
関数を直接呼んだり、DB を直接触ったりしていましたが、
E2E テストでは「ユーザーやクライアントが見るインターフェース(HTTP)」を通して確認します。


E2E テストの「おいしいところ」と「重さ」

何がうれしいのか

E2E テストの一番の価値は、
「ユーザー視点で、本当に動いているかを確認できる」ことです。

ルーティングの設定ミス
リクエスト/レスポンスの形式のズレ
認証・認可の抜け漏れ
ミドルウェアや設定の不整合

など、「実際に叩いてみないと分からない問題」を見つけやすくなります。

単体テスト・結合テストがどれだけ通っていても、
ルーティングが 404 になっていたらユーザーは使えません。
そこを守るのが E2E テストです。

ただし「重い・遅い・壊れやすい」側面もある

E2E テストは、範囲が広いぶん、どうしても重くなります。

実際の DB を使う
外部サービスのテスト用環境を使う
サーバーを立ち上げる(場合によっては Docker など)

などが絡むと、テスト時間も長くなり、
環境依存の問題も出やすくなります。

だからこそ、

全部を E2E でテストしようとしない
「本当に重要なシナリオ」に絞る

という考え方がとても大事です。


単体・結合・E2E の役割分担を整理する

3 つのレイヤーをイメージでつかむ

ざっくり、こういうイメージで考えると分かりやすいです。

単体テスト
関数・メソッド単位
ロジックの正しさ
速い・数が多くていい

結合テスト
モジュール+DB など
つながりの正しさ
そこそこ速い・重要な流れをカバー

E2E テスト
HTTP やブラウザ経由でシステム全体
ユーザー視点のシナリオの正しさ
遅い・数は絞る(本当に重要なシナリオだけ)

E2E テストだけに頼ると、
テストは遅くなり、バグの原因特定も難しくなります。

単体テストと結合テストで「細かいところ」を守り、
E2E テストで「全体として壊れていないか」を確認する、
という役割分担が現実的です。


E2E テストを始めるときの現実的なステップ

まずは「1 本の重要なシナリオ」を選ぶ

いきなり全部の画面・全部の API を E2E でテストしようとすると、
ほぼ確実に挫折します。

最初は、アプリの中で特に重要な 1 本を選びます。

ログイン
ユーザー登録
注文確定
決済完了

など、「これが壊れたら終わる」というやつです。

そのシナリオを、

テスト用の DB
テスト用の設定
テスト用クライアント(FastAPI の TestClient など)

を使って、コードで再現してみます。

「E2E でしか見つからないバグ」を意識する

E2E テストを書くときは、

単体テストでは気づけない
結合テストでも気づきにくい

ようなポイントを意識すると良いです。

例えば、

ルーティングのパスや HTTP メソッドの間違い
リクエストボディ/レスポンスボディの JSON 形式のズレ
認証がないとアクセスできないはずの API が誰でも叩けてしまう
逆に、ログインしているのに 401 が返ってくる

など、「実際に叩いてみないと分からない」部分です。


まとめ(E2E テストは「ユーザー目線の最後の砦」)

E2E テストを初心者目線で整理すると、こうなります。

E2E テストは、ユーザーの操作(またはクライアントからのリクエスト)を入口にして、システム全体を通して正しく動くかを確認するテスト。
単体テストが「部品」、結合テストが「内部の流れ」、E2E テストが「ユーザー視点のシナリオ」を守る役割を持つ。
E2E テストは強力だが重いので、「本当に重要なシナリオ」に絞って書くのが現実的。
FastAPI や Flask なら、テストクライアントを使って HTTP レベルの E2E に近いテストを、pytest から書ける。

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