📌 この記事はこんな方におすすめ
- PythonとrequestsでPOSTリクエストのテストを書きたい方
- APIテストでデータ作成の検証方法を学びたいQAエンジニアの方
- 正常系・バリデーション・異常系のPOSTテストを実装したい方
- pytestでAPIテストを自動化してポートフォリオに活用したい方
✅ この記事を読むとわかること
- pytestとrequestsでPOSTリクエストのAPIテストを書く基本パターン
- データ作成後のレスポンス検証(ステータス・ボディ・ヘッダー)
- バリデーションエラーの異常系テストの実装方法
- raise_for_status()を使った実務レベルのテストコードの書き方
PythonでAPIテストを書くとき、GETの次に実装するのがPOSTリクエストのテストです。pytestとrequestsを使って「データが正しく作成されるか?」「バリデーションエラーが正しく返るか?」を自動で検証する方法を解説します。
この記事では、POSTリクエストの基本から異常系・バリデーション検証までを実務で使えるコードとともに紹介します。
00. APIテストとPOSTリクエストの基本
POSTリクエストは「データを新規作成する」操作です。GETと違い、リクエストボディにデータを含めてサーバーに送信します。
POSTテストでは主に以下を検証します。
| 確認項目 | 内容 | 例 |
|---|---|---|
| ステータスコード | 作成成功時は201が返るか | 201 Created |
| レスポンスボディ | 送信したデータが正しく返るか | nameやemailの値 |
| IDの付与 | 新規作成されたリソースにIDが付与されているか | “id”: 11 |
| バリデーション | 不正なデータを送ったとき適切にエラーが返るか | 400 Bad Request |
💡 ポイント:POSTテストはGETより検証項目が多いです。正常系だけでなくバリデーションエラーの異常系も必ずテストすることで、APIの品質を担保できます。
01. pytest + requestsのAPIテスト環境
まだ環境構築が済んでいない方は先にインストールしてください。
pip install requests pytest pytest-html今回のテスト対象は無料のモックAPI JSONPlaceholder です。
POST https://jsonplaceholder.typicode.com/users送信するリクエストボディのサンプルです。
new_user = {
"name": "Yoshitsugu Tester",
"username": "yoshitsugu728",
"email": "yoshitsugu@example.com",
"phone": "090-1234-5678",
"website": "qa-auto-lab.com"
}💡 ポイント:JSONPlaceholderはモックAPIのため、実際にはデータが保存されません。ただしレスポンスは本番APIと同じ形式で返ってくるため、テスト学習に最適です。
02. PythonでAPIのPOSTテストを書く方法
基本的なPOSTテスト(201確認)
POSTリクエストを送り、201 Createdが返ることを確認します。
import requests
BASE_URL = "https://jsonplaceholder.typicode.com"
def test_post_user_status_code():
"""TC01: データ作成時にステータスコード201が返ること"""
new_user = {
"name": "Yoshitsugu Tester",
"username": "yoshitsugu728",
"email": "yoshitsugu@example.com"
}
response = requests.post(f"{BASE_URL}/users", json=new_user)
assert response.status_code == 201, \
f"期待値: 201, 実際の値: {response.status_code}"
print(f"\n✅ TC01 PASS | ステータスコード: {response.status_code}")raise_for_status()で4xx/5xxを確実に検知する
def test_post_user_raise_for_status():
"""TC02: 4xx/5xxエラー時に例外が発生すること"""
new_user = {"name": "Test User", "email": "test@example.com"}
response = requests.post(f"{BASE_URL}/users", json=new_user)
# 4xx/5xxのとき自動的にHTTPErrorが発生する
response.raise_for_status()
assert response.status_code == 201
print(f"\n✅ TC02 PASS | raise_for_status() 正常通過")💡 ポイント:response.raise_for_status() は実務で必須の1行です。POSTテストでも必ず追加しておきましょう。
ヘッダーの検証
def test_post_user_header():
"""TC03: Content-TypeがJSONであること"""
new_user = {"name": "Test User", "email": "test@example.com"}
response = requests.post(f"{BASE_URL}/users", json=new_user)
response.raise_for_status()
content_type = response.headers.get("Content-Type", "")
assert "application/json" in content_type, \
f"Content-Typeが想定外: {content_type}"
print(f"\n✅ TC03 PASS | Content-Type: {content_type}")💡 ポイント:Content-Typeは application/json; charset=utf-8 の形式で返ってくるため、== の完全一致ではなく in の部分一致で検証します。
レスポンスボディの検証
送信したデータがレスポンスに正しく含まれているか確認します。
def test_post_user_response_body():
"""TC04: 送信したデータがレスポンスに正しく含まれること"""
new_user = {
"name": "Yoshitsugu Tester",
"username": "yoshitsugu728",
"email": "yoshitsugu@example.com"
}
response = requests.post(f"{BASE_URL}/users", json=new_user)
response.raise_for_status()
body = response.json()
assert body["name"] == new_user["name"], f"name が違います: {body['name']}"
assert body["email"] == new_user["email"], f"email が違います: {body['email']}"
print(f"\n✅ TC04 PASS | 作成ユーザー: {body['name']}")IDが付与されているか確認
新規作成されたリソースにサーバーからIDが付与されているかを確認します。
def test_post_user_id_assigned():
"""TC05: 作成されたリソースにIDが付与されること"""
new_user = {"name": "Test User", "email": "test@example.com"}
response = requests.post(f"{BASE_URL}/users", json=new_user)
response.raise_for_status()
body = response.json()
assert "id" in body, "レスポンスにidが含まれていません"
assert isinstance(body["id"], int), f"idはint型が期待値: {type(body['id'])}"
assert body["id"] > 0, f"idは正の整数が期待値: {body['id']}"
print(f"\n✅ TC05 PASS | 付与されたID: {body['id']}")💡 ポイント:IDの付与確認は実務でよく見落とされるテストです。サーバーがIDを自動採番しているか、型は正しいかを合わせて確認しましょう。
03. APIテストのPOSTバリデーション検証
実務では正常系だけでなく、不正なデータを送ったときに適切にエラーが返るかも必ず検証します。
必須フィールドなしで送信(バリデーションエラー確認)
def test_post_user_missing_required_fields():
"""TC06: 必須フィールドなしで送信したとき適切なエラーが返ること"""
incomplete_user = {
"username": "yoshitsugu728"
# name と email を意図的に省略
}
response = requests.post(f"{BASE_URL}/users", json=incomplete_user)
# JSONPlaceholderはモックのため201を返す
# 実際のAPIでは400 Bad Requestが期待される
assert response.status_code in [201, 400], \
f"期待値: 201 または 400, 実際の値: {response.status_code}"
print(f"\n✅ TC06 PASS | 必須フィールドなし時のステータス: {response.status_code}")空文字列を送信
def test_post_user_empty_string():
"""TC07: 空文字列を送信したとき適切なエラーが返ること"""
invalid_user = {
"name": "", # 空文字
"email": "" # 空文字
}
response = requests.post(f"{BASE_URL}/users", json=invalid_user)
assert response.status_code in [201, 400, 422], \
f"期待外のステータスコード: {response.status_code}"
print(f"\n✅ TC07 PASS | 空文字送信時のステータス: {response.status_code}")不正なメール形式を送信
def test_post_user_invalid_email():
"""TC08: 不正なメール形式を送信したとき適切なエラーが返ること"""
invalid_user = {
"name": "Test User",
"email": "not-an-email" # メール形式でない
}
response = requests.post(f"{BASE_URL}/users", json=invalid_user)
assert response.status_code in [201, 400, 422], \
f"期待外のステータスコード: {response.status_code}"
print(f"\n✅ TC08 PASS | 不正メール送信時のステータス: {response.status_code}")⚠️ 注意:JSONPlaceholderはモックAPIのため、バリデーションエラーに対しても201を返します。実際のAPIでは400や422が返ることを検証してください。
04. APIテストのPOSTスキーマ検証
作成されたリソースのレスポンスが正しい型かどうかも確認します。
def test_post_user_schema():
"""TC09: レスポンスのスキーマ(型)が正しいこと"""
new_user = {
"name": "Yoshitsugu Tester",
"email": "yoshitsugu@example.com"
}
response = requests.post(f"{BASE_URL}/users", json=new_user)
response.raise_for_status()
body = response.json()
assert isinstance(body["id"], int), f"idはint型が期待値: {type(body['id'])}"
assert isinstance(body["name"], str), f"nameはstr型が期待値: {type(body['name'])}"
print(f"\n✅ TC09 PASS | スキーマ検証完了")05. POSTテストのコード全文
"""
POST API Test
Target: JSONPlaceholder (https://jsonplaceholder.typicode.com)
Framework: Python + requests + pytest
"""
import requests
BASE_URL = "https://jsonplaceholder.typicode.com"
def test_post_user_status_code():
"""TC01: データ作成時にステータスコード201が返ること"""
new_user = {"name": "Yoshitsugu Tester", "email": "yoshitsugu@example.com"}
response = requests.post(f"{BASE_URL}/users", json=new_user)
assert response.status_code == 201, \
f"期待値: 201, 実際の値: {response.status_code}"
print(f"\n✅ TC01 PASS | status: {response.status_code}")
def test_post_user_raise_for_status():
"""TC02: 4xx/5xxエラー時に例外が発生すること"""
new_user = {"name": "Test User", "email": "test@example.com"}
response = requests.post(f"{BASE_URL}/users", json=new_user)
response.raise_for_status()
assert response.status_code == 201
print(f"\n✅ TC02 PASS | raise_for_status() 正常通過")
def test_post_user_header():
"""TC03: Content-TypeがJSONであること"""
new_user = {"name": "Test User", "email": "test@example.com"}
response = requests.post(f"{BASE_URL}/users", json=new_user)
response.raise_for_status()
content_type = response.headers.get("Content-Type", "")
assert "application/json" in content_type, \
f"Content-Typeが想定外: {content_type}"
print(f"\n✅ TC03 PASS | Content-Type: {content_type}")
def test_post_user_response_body():
"""TC04: 送信したデータがレスポンスに正しく含まれること"""
new_user = {"name": "Yoshitsugu Tester", "email": "yoshitsugu@example.com"}
response = requests.post(f"{BASE_URL}/users", json=new_user)
response.raise_for_status()
body = response.json()
assert body["name"] == new_user["name"]
assert body["email"] == new_user["email"]
print(f"\n✅ TC04 PASS | name: {body['name']}")
def test_post_user_id_assigned():
"""TC05: 作成されたリソースにIDが付与されること"""
new_user = {"name": "Test User", "email": "test@example.com"}
response = requests.post(f"{BASE_URL}/users", json=new_user)
response.raise_for_status()
body = response.json()
assert "id" in body
assert isinstance(body["id"], int)
assert body["id"] > 0
print(f"\n✅ TC05 PASS | 付与されたID: {body['id']}")
def test_post_user_missing_required_fields():
"""TC06: 必須フィールドなしで送信したとき適切なエラーが返ること"""
incomplete_user = {"username": "yoshitsugu728"}
response = requests.post(f"{BASE_URL}/users", json=incomplete_user)
assert response.status_code in [201, 400]
print(f"\n✅ TC06 PASS | status: {response.status_code}")
def test_post_user_empty_string():
"""TC07: 空文字列を送信したとき適切なエラーが返ること"""
invalid_user = {"name": "", "email": ""}
response = requests.post(f"{BASE_URL}/users", json=invalid_user)
assert response.status_code in [201, 400, 422]
print(f"\n✅ TC07 PASS | status: {response.status_code}")
def test_post_user_invalid_email():
"""TC08: 不正なメール形式を送信したとき適切なエラーが返ること"""
invalid_user = {"name": "Test User", "email": "not-an-email"}
response = requests.post(f"{BASE_URL}/users", json=invalid_user)
assert response.status_code in [201, 400, 422]
print(f"\n✅ TC08 PASS | status: {response.status_code}")
def test_post_user_schema():
"""TC09: レスポンスのスキーマ(型)が正しいこと"""
new_user = {"name": "Yoshitsugu Tester", "email": "yoshitsugu@example.com"}
response = requests.post(f"{BASE_URL}/users", json=new_user)
response.raise_for_status()
body = response.json()
assert isinstance(body["id"], int)
assert isinstance(body["name"], str)
print(f"\n✅ TC09 PASS | スキーマ検証完了")実行コマンド
pytest test_post_api.py -v -s実行結果サンプル
test_post_api.py::test_post_user_status_code PASSED
test_post_api.py::test_post_user_raise_for_status PASSED
test_post_api.py::test_post_user_header PASSED
test_post_api.py::test_post_user_response_body PASSED
test_post_api.py::test_post_user_id_assigned PASSED
test_post_api.py::test_post_user_missing_required_fields PASSED
test_post_api.py::test_post_user_empty_string PASSED
test_post_api.py::test_post_user_invalid_email PASSED
test_post_api.py::test_post_user_schema PASSED
9 passed in 4.12s ✅06. ハマりポイント
実装中に実際につまずいた箇所をまとめました。同じところでハマる方の参考になれば嬉しいです。
① json=とdata=の違いで詰まる
requestsでPOSTリクエストを送るとき、json= と data= の違いを知らずにハマりました。data= を使うとContent-Typeが application/x-www-form-urlencoded になってしまいJSON APIで失敗します。
# ❌ data=を使うとJSON形式で送信されない
response = requests.post(url, data={"name": "Taro"})
# ✅ json=を使うと自動的にContent-Type: application/jsonが設定される
response = requests.post(url, json={"name": "Taro"})💡 ポイント:json= を使うとリクエストボディの変換とContent-Typeの設定が自動で行われるため、JSON APIには必ず json= を使いましょう。
② POSTのステータスコードが200か201か迷う
POSTの成功レスポンスが200なのか201なのかで迷いました。APIの設計によって異なりますが、REST APIの慣習では新規作成は201が一般的です。
# ❌ 固定で200を期待してしまう
assert response.status_code == 200
# ✅ APIの仕様に合わせて確認する
# REST APIの慣習:作成成功は201
assert response.status_code == 201
# 仕様が曖昧な場合は両方を許容する書き方も可
assert response.status_code in [200, 201]💡 ポイント:テストを書く前にAPIドキュメントで期待するステータスコードを確認しましょう。
③ レスポンスボディに送信したデータが含まれない
POSTのレスポンスには送信したデータがそのまま返ってくると思っていましたが、サーバー側で加工されたデータが返ってくるケースがありました。
# ❌ 送信データと完全一致を期待してしまう
assert body == new_user # サーバー側でidが追加されるため失敗
# ✅ 必要なフィールドだけを個別に検証する
assert body["name"] == new_user["name"]
assert body["email"] == new_user["email"]
assert "id" in body # サーバーが付与したIDも確認💡 ポイント:POSTのレスポンスにはサーバーが付与したIDや自動生成フィールドが追加されます。完全一致ではなく必要なフィールドを個別に検証する方が堅牢です。
④ バリデーションエラーのステータスコードが400か422か迷う
バリデーションエラー時のステータスコードが400なのか422なのかで迷うことがありました。
# ❌ 400だけを期待してしまう
assert response.status_code == 400
# ✅ APIの仕様に応じて両方を許容する
assert response.status_code in [400, 422]
# 400 Bad Request → 一般的なリクエストエラー
# 422 Unprocessable → バリデーションエラー(FastAPI等で多用)💡 ポイント:バリデーションエラーのステータスコードはフレームワークによって異なります。Expressは400、FastAPIは422を返すことが多いです。APIドキュメントで確認しましょう。
⑤ モックAPIでバリデーションが検証できない
JSONPlaceholderでバリデーションテストを書いたところ、不正なデータを送っても201が返ってきてしまい、テストの意味がなくなりました。
# JSONPlaceholderはモックのため
# 不正なデータを送っても201が返ってしまう
response = requests.post(url, json={"email": "not-an-email"})
print(response.status_code) # → 201(本来は400が期待される)⚠️ 注意:バリデーションテストを正確に行うには実際のバリデーション機能を持つAPIが必要です。Restful Booker や reqres.in などを使うか、自前のAPIで検証しましょう。
07. よくある質問(FAQ)
Q. POSTテストで最低限確認すべきことは何ですか?
A. 最低限「ステータスコードが201であること」「送信したデータがレスポンスに含まれること」「IDが付与されていること」の3つです。余裕があればバリデーション・スキーマ検証・ヘッダー確認も追加すると実務レベルのテストになります。
Q. requests.post()でJSONを送る方法は?
A. requests.post(url, json=データ) を使います。json= を使うとContent-Typeが自動的に application/json に設定されます。data= を使うとフォームデータとして送信されてしまうため、JSON APIには必ず json= を使いましょう。
Q. GETテストとPOSTテストの違いは何ですか?
A. GETは「データの取得」、POSTは「データの作成」です。POSTテストはリクエストボディを送信する点、成功時のステータスコードが201である点、バリデーションエラーの検証が必要な点がGETと異なります。
Q. バリデーションテストはどのAPIで試せますか?
A. JSONPlaceholderはモックAPIのためバリデーション機能がありません。本格的なバリデーションテストには reqres.in や自前のAPIを使いましょう。ポートフォリオ用には Restful Booker の認証エンドポイントも使えます。
Q. POSTとPUTの違いは何ですか?
A. POSTは「新規作成」、PUTは「既存データの全体更新」です。POSTは毎回新しいリソースが作られるのに対し、PUTは同じリクエストを何度送っても同じ結果になります(冪等性)。次の記事でPUT・PATCHテストを解説します。
08. まとめ
PythonでAPIのPOSTテストを実装する場合、pytestとrequestsを使ってステータスコード・レスポンスボディ・IDの付与・バリデーションの4つを検証することが実務の基本です。
この記事では、pytestとrequestsを使ったAPIのPOSTテスト実装方法を解説しました。
| テストケース | 内容 |
|---|---|
| TC01 | ステータスコードが201であること |
| TC02 | raise_for_status()で4xx/5xxを検知 |
| TC03 | Content-TypeがJSONであること |
| TC04 | 送信データがレスポンスに正しく含まれること |
| TC05 | 作成されたリソースにIDが付与されること |
| TC06 | 必須フィールドなしで適切なエラーが返ること |
| TC07 | 空文字列送信で適切なエラーが返ること |
| TC08 | 不正なメール形式で適切なエラーが返ること |
| TC09 | レスポンスのスキーマ(型)が正しいこと |
次の記事ではPUT・PATCHリクエストのAPIテスト(既存データの更新・部分更新)を解説します。

