PythonでAPIのDELETEテストを書く方法|pytest×requestsで削除を検証

テスト自動化

📌 この記事はこんな方におすすめ

  • PythonとrequestsでDELETEリクエストのテストを書きたい方
  • APIテストでデータ削除の検証方法を学びたいQAエンジニアの方
  • CRUD操作のAPIテストを完成させたい方
  • 削除後の404確認まで含めた堅牢なDELETEテストを実装したい方

この記事を読むとわかること

  • pytestとrequestsでDELETEリクエストのAPIテストを書く基本パターン
  • 削除成功時のステータスコード(200・204)の違いと使い分け
  • 削除後に404が返ることを確認する実装方法
  • raise_for_status()を使った実務レベルのテストコードの書き方

👨‍💻 筆者について

QAエンジニアとして実務でPython・pytest・requestsを使ったAPIテスト自動化を担当。本記事で使用するコードはすべてGitHubで公開しており、実際に動作確認済みのコードをそのまま解説しています。GitHubでコードを見る →

PythonでAPIテストを書くとき、CRUD操作の最後がDELETEリクエストのテストです。pytestとrequestsを使えば、APIの削除処理が正しく動くかを自動テストで検証できます。

この記事では、DELETEテストの基本から削除後の404確認までを実務で使えるコードとともに紹介します。


00. APIテストとDELETEリクエストの基本

DELETEリクエストは「リソースを削除する」操作です。GETやPOSTと比べてシンプルですが、削除後のリソースの状態確認が重要です。

確認項目内容
ステータスコード削除成功時は200か204が返るか200 OK / 204 No Content
削除後の確認削除後にGETすると404が返るか404 Not Found
存在しないリソース存在しないIDを削除しようとしたとき適切なエラーが返るか404 Not Found
レスポンスボディ204の場合はボディが空か{} または 空

💡 ポイント:DELETEテストは「削除できた」だけでなく「削除後にアクセスできなくなった」まで確認することで、本当の意味でのテストになります。


01. pytest + requestsのAPIテスト環境

まだ環境構築が済んでいない方は先にインストールしてください。

pip install requests pytest pytest-html

今回のテスト対象は無料のモックAPI JSONPlaceholder です。

DELETE https://jsonplaceholder.typicode.com/users/1

💡 ポイント:JSONPlaceholderはモックAPIのため、DELETEを実行しても実際のデータは削除されません。そのため削除後にGETしても200が返ることがあります。実際のAPIでは削除後のGETで404が返ることを期待してテストを書くケースが多いです。


02. PythonでAPIのDELETEテストを書く方法

基本的なDELETEテスト(200確認)

DELETEリクエストを送り、200が返ることを確認します。

import requests

BASE_URL = "https://jsonplaceholder.typicode.com"

def test_delete_user_status_code():
    """TC01: DELETEで削除時にステータスコード200が返ること"""
    response = requests.delete(f"{BASE_URL}/users/1")

    assert response.status_code == 200, \
        f"期待値: 200, 実際の値: {response.status_code}"

    print(f"\n✅ TC01 PASS | ステータスコード: {response.status_code}")

raise_for_status()で4xx/5xxを確実に検知する

def test_delete_user_raise_for_status():
    """TC02: 4xx/5xxエラー時に例外が発生すること"""
    response = requests.delete(f"{BASE_URL}/users/1")

    response.raise_for_status()

    assert response.status_code == 200
    print(f"\n✅ TC02 PASS | raise_for_status() 正常通過")

💡 ポイント:raise_for_status() を使うと4xx/5xxを例外として検知できます。ただしAPIテストでは意図的に4xx/5xxを確認するケースもあるため、status_codeのassertと使い分けることが重要です。

レスポンスボディの確認

DELETEのレスポンスボディは空か、空のJSONが返ることを確認します。

def test_delete_user_response_body():
    """TC03: DELETEのレスポンスボディが空または空JSONであること"""
    response = requests.delete(f"{BASE_URL}/users/1")

    response.raise_for_status()

    # 204 No Contentのときはjson()を呼ぶとJSONDecodeErrorになるため分岐
    if response.status_code == 204:
        body = None
    else:
        body = response.json()

    assert body in [None, {}], \
        f"削除後のレスポンスボディは空が期待値: {body}"

    print(f"\n✅ TC03 PASS | レスポンスボディ: {body}")

💡 ポイント:DELETEのレスポンスボディはAPIの設計によって異なります。空のJSON `{}`・空文字・204 No Content(ボディなし)のパターンがあります。


03. APIテストのDELETE後の状態確認

削除テストで最も重要なのが「削除後にリソースにアクセスできなくなっているか」の確認です。

削除後にGETすると404が返ることを確認

def test_delete_then_get_returns_404():
    """TC04: 削除後にGETすると404が返ること"""
    # Step1: リソースを削除
    delete_response = requests.delete(f"{BASE_URL}/users/1")
    assert delete_response.status_code == 200, "削除が失敗しました"

    # Step2: 削除後にGETしてみる
    get_response = requests.get(f"{BASE_URL}/users/1")

    # JSONPlaceholderはモックのため200が返ることもある
    # 実際のAPIでは404が返ることを検証する
    assert get_response.status_code in [200, 404], \
        f"削除後のGETは404が期待値: {get_response.status_code}"

    print(f"\n✅ TC04 PASS | 削除後のGETステータス: {get_response.status_code}")

💡 ポイント:削除後に404が返ることの確認は2ステップのテスト(DELETE→GET)で実現します。これがDELETEテストで最も重要な検証です。


04. APIテストのDELETE異常系パターン

存在しないリソースの削除(404確認)

def test_delete_nonexistent_user():
    """TC05: 存在しないリソースの削除で404が返ること"""
    response = requests.delete(f"{BASE_URL}/users/99999")

    # JSONPlaceholderはモックのため200が返ることもある
    # 実際のAPIでは404が期待される
    assert response.status_code in [200, 404], \
        f"期待外のステータスコード: {response.status_code}"

    print(f"\n✅ TC05 PASS | 存在しないリソースの削除: {response.status_code}")

⚠️ 注意:JSONPlaceholderはモックAPIのため、存在しないIDに対しても200を返すことがあります。実際のAPIでは404が返ることを検証してください。

200と204の両方に対応したテスト

def test_delete_user_accepts_200_or_204():
    """TC06: 削除成功時に200または204が返ること"""
    response = requests.delete(f"{BASE_URL}/users/1")

    # REST APIの慣習:削除成功は200または204
    assert response.status_code in [200, 204], \
        f"削除成功時は200か204が期待値: {response.status_code}"

    print(f"\n✅ TC06 PASS | ステータスコード: {response.status_code}")

💡 ポイント:削除成功時のステータスコードはAPIの設計によって200か204が返ります。どちらにも対応できる書き方にしておくと汎用性が上がります。


05. APIテストのDELETEヘッダー検証

def test_delete_user_header():
    """TC07: Content-TypeがJSONであること(204以外の場合)"""
    response = requests.delete(f"{BASE_URL}/users/1")

    response.raise_for_status()

    # 204 No ContentはContent-Typeヘッダーが存在しないことがある
    if response.status_code != 204:
        content_type = response.headers.get("Content-Type", "")
        assert "application/json" in content_type, \
            f"Content-Typeが想定外: {content_type}"
        print(f"\n✅ TC07 PASS | Content-Type: {content_type}")
    else:
        print(f"\n✅ TC07 PASS | 204 No Content のためContent-Typeチェックをスキップ")

06. DELETEテストのコード全文

"""
DELETE API Test
Target: JSONPlaceholder (https://jsonplaceholder.typicode.com)
Framework: Python + requests + pytest
"""
import requests

BASE_URL = "https://jsonplaceholder.typicode.com"


def test_delete_user_status_code():
    """TC01: DELETEで削除時にステータスコード200が返ること"""
    response = requests.delete(f"{BASE_URL}/users/1")
    assert response.status_code == 200, \
        f"期待値: 200, 実際の値: {response.status_code}"
    print(f"\n✅ TC01 PASS | status: {response.status_code}")


def test_delete_user_raise_for_status():
    """TC02: 4xx/5xxエラー時に例外が発生すること"""
    response = requests.delete(f"{BASE_URL}/users/1")
    response.raise_for_status()
    assert response.status_code == 200
    print(f"\n✅ TC02 PASS | raise_for_status() 正常通過")


def test_delete_user_response_body():
    """TC03: DELETEのレスポンスボディが空または空JSONであること"""
    response = requests.delete(f"{BASE_URL}/users/1")
    response.raise_for_status()
    if response.status_code == 204:
        body = None
    else:
        body = response.json()
    assert body in [None, {}]
    print(f"\n✅ TC03 PASS | body: {body}")


def test_delete_then_get_returns_404():
    """TC04: 削除後にGETすると404が返ること"""
    delete_response = requests.delete(f"{BASE_URL}/users/1")
    assert delete_response.status_code == 200
    get_response = requests.get(f"{BASE_URL}/users/1")
    assert get_response.status_code in [200, 404]
    print(f"\n✅ TC04 PASS | 削除後GETステータス: {get_response.status_code}")


def test_delete_nonexistent_user():
    """TC05: 存在しないリソースの削除で404が返ること"""
    response = requests.delete(f"{BASE_URL}/users/99999")
    assert response.status_code in [200, 404]
    print(f"\n✅ TC05 PASS | status: {response.status_code}")


def test_delete_user_accepts_200_or_204():
    """TC06: 削除成功時に200または204が返ること"""
    response = requests.delete(f"{BASE_URL}/users/1")
    assert response.status_code in [200, 204]
    print(f"\n✅ TC06 PASS | status: {response.status_code}")


def test_delete_user_header():
    """TC07: Content-TypeがJSONであること(204以外の場合)"""
    response = requests.delete(f"{BASE_URL}/users/1")
    response.raise_for_status()
    if response.status_code != 204:
        content_type = response.headers.get("Content-Type", "")
        assert "application/json" in content_type
        print(f"\n✅ TC07 PASS | Content-Type: {content_type}")
    else:
        print(f"\n✅ TC07 PASS | 204のためスキップ")

実行コマンド

pytest test_delete_api.py -v -s

実行結果サンプル

test_delete_api.py::test_delete_user_status_code        PASSED
test_delete_api.py::test_delete_user_raise_for_status   PASSED
test_delete_api.py::test_delete_user_response_body      PASSED
test_delete_api.py::test_delete_then_get_returns_404    PASSED
test_delete_api.py::test_delete_nonexistent_user        PASSED
test_delete_api.py::test_delete_user_accepts_200_or_204 PASSED
test_delete_api.py::test_delete_user_header             PASSED

7 passed in 3.56s ✅

07. ハマりポイント

実装中に実際につまずいた箇所をまとめました。同じところでハマる方の参考になれば嬉しいです。


① 削除成功が200か204か迷う

DELETEの成功レスポンスが200なのか204なのかで迷いました。APIの設計によって異なります。

# ❌ 固定で200だけを期待してしまう
assert response.status_code == 200

# ✅ 200と204の両方を許容する
assert response.status_code in [200, 204]
# 200 OK         → レスポンスボディあり(削除したリソースの情報など)
# 204 No Content → レスポンスボディなし(シンプルな削除確認)

💡 ポイント:テストを書く前にAPIドキュメントで期待するステータスコードを確認しましょう。JSONPlaceholderは200を返します。


② 204のとき response.json() でエラーが出る

204 No Contentのレスポンスで response.json() を呼んだところ、ボディが空のためエラーになりました。

# ❌ 204のときにjson()を呼ぶとエラーになる
body = response.json()  # JSONDecodeError!

# ✅ ステータスコードで分岐する
if response.status_code == 204:
    print("レスポンスボディなし(204 No Content)")
else:
    body = response.json()
    print(f"レスポンスボディ: {body}")

⚠️ 注意:204 No Contentはレスポンスボディが存在しません。ステータスコードを確認してから json() を呼ぶようにしましょう。


③ モックAPIで削除後の404が確認できない

JSONPlaceholderでDELETE後にGETしても200が返ってきてしまい、削除後の404確認ができませんでした。

# JSONPlaceholderはモックのため
# DELETE後にGETしても200が返ってしまう
requests.delete(f"{BASE_URL}/users/1")
response = requests.get(f"{BASE_URL}/users/1")
print(response.status_code)  # → 200(本来は404が期待される)

💡 ポイント:削除後の404確認は実際にデータが削除されるAPIで行う必要があります。Restful Bookerや自前のAPIで検証しましょう。


④ DELETEでリクエストボディを送ってしまう

DELETEリクエストにリクエストボディを含めて送ろうとしましたが、DELETEはURLでリソースを指定するため通常ボディは不要です。

# ❌ DELETEにボディを含めるのは多くのREST APIでは一般的でない
response = requests.delete(url, json={"id": 1})

# ✅ DELETEはURLのみで指定するのが基本
response = requests.delete(f"{BASE_URL}/users/1")

💡 ポイント:DELETEは通常URLパラメータで削除対象を指定するため、多くのREST APIではリクエストボディは不要です。ただしElasticsearchや一部のAPIでは削除条件や削除理由をボディで送る設計もあります。APIドキュメントで確認しましょう。


⑤ 削除済みリソースを再削除したときの挙動を確認し忘れる

一度削除したリソースを再度削除しようとしたとき、どんなレスポンスが返るかの確認を忘れていました。

# 削除済みリソースを再削除した場合
# 実際のAPIでは 404 Not Found が返ることが多い
response1 = requests.delete(f"{BASE_URL}/users/1")  # 1回目:成功(200)
response2 = requests.delete(f"{BASE_URL}/users/1")  # 2回目:既に存在しない

assert response1.status_code == 200
# 実際のAPIでは2回目は404が期待される
assert response2.status_code in [200, 404]

💡 ポイント:削除済みリソースへの再削除テストは実際のAPIでの検証が必要です。モックAPIでは確認できません。


08. DELETE APIテストのベストプラクティス

DELETE APIテストでは以下の3つを確認することが重要です。

確認項目内容重要度
① ステータスコード確認削除成功時は200または204が返ること⭐⭐⭐ 必須
② 削除後の404確認削除後にGETすると404が返ること⭐⭐⭐ 重要
③ 異常系の確認存在しないリソース削除時に適切なエラーが返ること⭐⭐ 推奨

この3点を確認することでDELETE APIの品質を高いレベルで保証できます。特に削除後にGETすると404が返ることの確認は、単に「削除できた」だけでなく「削除後にアクセスできなくなった」まで検証するため、実務では必ず実施しましょう。

💡 ポイント:GETテスト・POSTテスト・PUT/PATCHテスト・DELETEテストの4種類を実装することで、CRUD全操作をカバーした完全なAPIテストスイートが完成します。これはポートフォリオとしても非常に強力なアピールになります。


09. よくある質問(FAQ)

Q. DELETEテストで最低限確認すべきことは何ですか?
A. 最低限「ステータスコードが200または204であること」の1つです。余裕があれば「削除後にGETすると404が返ること」も追加すると実務レベルのテストになります。

Q. DELETEのステータスコードは200と204どちらが正しいですか?
A. どちらも正しいです。APIの設計によります。削除後にリソースの情報を返す場合は200、何も返さない場合は204が使われることが多いです。テストを書く前にAPIドキュメントで確認してください。

Q. DELETEはリクエストボディが必要ですか?
A. 通常は不要です。DELETEはURLのパスパラメータで削除対象を指定します(例:/users/1)。ただし一部のAPIでは削除理由などをボディに含めることもあります。

Q. 削除後の404確認はどのAPIで試せますか?
A. JSONPlaceholderはモックAPIのため削除後の404確認ができません。reqres.in や自前のAPIを使うか、Restful Bookerの予約削除エンドポイントで試すことができます。

Q. CRUDテストを全部書くと何件になりますか?
A. 今回のシリーズで実装したテストケースはGET(9件)・POST(9件)・PUT/PATCH(9件)・DELETE(7件)の合計34件です。これだけあれば「CRUDが全部できる」というポートフォリオとして十分なアピールになります。


10. まとめ

PythonでAPIのDELETEテストを実装する場合、pytestとrequestsを使ってステータスコードと削除後の状態確認が実務の基本です。DELETEはシンプルなテストですが、削除後に404が返ることまで確認することで完全なCRUDテストになります。

この記事では、pytestとrequestsを使ったAPIのDELETEテスト実装方法を解説しました。

テストケース内容
TC01DELETEで削除時にステータスコード200が返ること
TC02raise_for_status()で4xx/5xxを検知
TC03DELETEのレスポンスボディが空または空JSONであること
TC04削除後にGETすると404が返ること
TC05存在しないリソースの削除で404が返ること
TC06削除成功時に200または204が返ること
TC07Content-TypeがJSONであること

これでGET・POST・PUT/PATCH・DELETEのCRUD全操作のAPIテストが完成しました。次の記事ではシリーズのまとめとして、実務でのAPIテスト設計の考え方を解説します。

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