🔬¶
👏 💃, 🔬 FastAPI 🈸 ⏩ & 😌.
⚫️ ⚓️ 🔛 🇸🇲, ❔ 🔄 🏗 ⚓️ 🔛 📨, ⚫️ 📶 😰 & 🏋️.
⏮️ ⚫️, 👆 💪 ⚙️ ✳ 🔗 ⏮️ FastAPI.
⚙️ TestClient
¶
🗄 TestClient
.
✍ TestClient
🚶♀️ 👆 FastAPI 🈸 ⚫️.
✍ 🔢 ⏮️ 📛 👈 ▶️ ⏮️ test_
(👉 🐩 pytest
🏛).
⚙️ TestClient
🎚 🎏 🌌 👆 ⏮️ httpx
.
✍ 🙅 assert
📄 ⏮️ 🐩 🐍 🧬 👈 👆 💪 ✅ (🔄, 🐩 pytest
).
from fastapi import FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
@app.get("/")
async def read_main():
return {"msg": "Hello World"}
client = TestClient(app)
def test_read_main():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"msg": "Hello World"}
Tip
👀 👈 🔬 🔢 😐 def
, 🚫 async def
.
& 🤙 👩💻 😐 🤙, 🚫 ⚙️ await
.
👉 ✔ 👆 ⚙️ pytest
🔗 🍵 🤢.
📡 ℹ
👆 💪 ⚙️ from starlette.testclient import TestClient
.
FastAPI 🚚 🎏 starlette.testclient
fastapi.testclient
🏪 👆, 👩💻. ✋️ ⚫️ 👟 🔗 ⚪️➡️ 💃.
Tip
🚥 👆 💚 🤙 async
🔢 👆 💯 ↖️ ⚪️➡️ 📨 📨 👆 FastAPI 🈸 (✅ 🔁 💽 🔢), ✔️ 👀 🔁 💯 🏧 🔰.
🎏 💯¶
🎰 🈸, 👆 🎲 🔜 ✔️ 👆 💯 🎏 📁.
& 👆 FastAPI 🈸 5️⃣📆 ✍ 📚 📁/🕹, ♒️.
FastAPI 📱 📁¶
➡️ 💬 👆 ✔️ 📁 📊 🔬 🦏 🈸:
.
├── app
│ ├── __init__.py
│ └── main.py
📁 main.py
👆 ✔️ 👆 FastAPI 📱:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_main():
return {"msg": "Hello World"}
🔬 📁¶
⤴️ 👆 💪 ✔️ 📁 test_main.py
⏮️ 👆 💯. ⚫️ 💪 🖖 🔛 🎏 🐍 📦 (🎏 📁 ⏮️ __init__.py
📁):
.
├── app
│ ├── __init__.py
│ ├── main.py
│ └── test_main.py
↩️ 👉 📁 🎏 📦, 👆 💪 ⚙️ ⚖ 🗄 🗄 🎚 app
⚪️➡️ main
🕹 (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() == {"msg": "Hello World"}
...& ✔️ 📟 💯 💖 ⏭.
🔬: ↔ 🖼¶
🔜 ➡️ ↔ 👉 🖼 & 🚮 🌖 ℹ 👀 ❔ 💯 🎏 🍕.
↔ FastAPI 📱 📁¶
➡️ 😣 ⏮️ 🎏 📁 📊 ⏭:
.
├── app
│ ├── __init__.py
│ ├── main.py
│ └── test_main.py
➡️ 💬 👈 🔜 📁 main.py
⏮️ 👆 FastAPI 📱 ✔️ 🎏 ➡ 🛠️.
⚫️ ✔️ GET
🛠️ 👈 💪 📨 ❌.
⚫️ ✔️ POST
🛠️ 👈 💪 📨 📚 ❌.
👯♂️ ➡ 🛠️ 🚚 X-Token
🎚.
from typing import Union
from fastapi import FastAPI, Header, HTTPException
from pydantic import BaseModel
fake_secret_token = "coneofsilence"
fake_db = {
"foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
"bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
}
app = FastAPI()
class Item(BaseModel):
id: str
title: str
description: Union[str, None] = None
@app.get("/items/{item_id}", response_model=Item)
async def read_main(item_id: str, x_token: str = Header()):
if x_token != fake_secret_token:
raise HTTPException(status_code=400, detail="Invalid X-Token header")
if item_id not in fake_db:
raise HTTPException(status_code=404, detail="Item not found")
return fake_db[item_id]
@app.post("/items/", response_model=Item)
async def create_item(item: Item, x_token: str = Header()):
if x_token != fake_secret_token:
raise HTTPException(status_code=400, detail="Invalid X-Token header")
if item.id in fake_db:
raise HTTPException(status_code=409, detail="Item already exists")
fake_db[item.id] = item
return item
from fastapi import FastAPI, Header, HTTPException
from pydantic import BaseModel
fake_secret_token = "coneofsilence"
fake_db = {
"foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
"bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
}
app = FastAPI()
class Item(BaseModel):
id: str
title: str
description: str | None = None
@app.get("/items/{item_id}", response_model=Item)
async def read_main(item_id: str, x_token: str = Header()):
if x_token != fake_secret_token:
raise HTTPException(status_code=400, detail="Invalid X-Token header")
if item_id not in fake_db:
raise HTTPException(status_code=404, detail="Item not found")
return fake_db[item_id]
@app.post("/items/", response_model=Item)
async def create_item(item: Item, x_token: str = Header()):
if x_token != fake_secret_token:
raise HTTPException(status_code=400, detail="Invalid X-Token header")
if item.id in fake_db:
raise HTTPException(status_code=409, detail="Item already exists")
fake_db[item.id] = item
return item
↔ 🔬 📁¶
👆 💪 ⤴️ ℹ test_main.py
⏮️ ↔ 💯:
from fastapi.testclient import TestClient
from .main import app
client = TestClient(app)
def test_read_item():
response = client.get("/items/foo", headers={"X-Token": "coneofsilence"})
assert response.status_code == 200
assert response.json() == {
"id": "foo",
"title": "Foo",
"description": "There goes my hero",
}
def test_read_item_bad_token():
response = client.get("/items/foo", headers={"X-Token": "hailhydra"})
assert response.status_code == 400
assert response.json() == {"detail": "Invalid X-Token header"}
def test_read_inexistent_item():
response = client.get("/items/baz", headers={"X-Token": "coneofsilence"})
assert response.status_code == 404
assert response.json() == {"detail": "Item not found"}
def test_create_item():
response = client.post(
"/items/",
headers={"X-Token": "coneofsilence"},
json={"id": "foobar", "title": "Foo Bar", "description": "The Foo Barters"},
)
assert response.status_code == 200
assert response.json() == {
"id": "foobar",
"title": "Foo Bar",
"description": "The Foo Barters",
}
def test_create_item_bad_token():
response = client.post(
"/items/",
headers={"X-Token": "hailhydra"},
json={"id": "bazz", "title": "Bazz", "description": "Drop the bazz"},
)
assert response.status_code == 400
assert response.json() == {"detail": "Invalid X-Token header"}
def test_create_existing_item():
response = client.post(
"/items/",
headers={"X-Token": "coneofsilence"},
json={
"id": "foo",
"title": "The Foo ID Stealers",
"description": "There goes my stealer",
},
)
assert response.status_code == 409
assert response.json() == {"detail": "Item already exists"}
🕐❔ 👆 💪 👩💻 🚶♀️ ℹ 📨 & 👆 🚫 💭 ❔, 👆 💪 🔎 (🇺🇸🔍) ❔ ⚫️ httpx
, ⚖️ ❔ ⚫️ ⏮️ requests
, 🇸🇲 🔧 ⚓️ 🔛 📨' 🔧.
⤴️ 👆 🎏 👆 💯.
🤶 Ⓜ.:
- 🚶♀️ ➡ ⚖️ 🔢 🔢, 🚮 ⚫️ 📛 ⚫️.
- 🚶♀️ 🎻 💪, 🚶♀️ 🐍 🎚 (✅
dict
) 🔢json
. - 🚥 👆 💪 📨 📨 💽 ↩️ 🎻, ⚙️
data
🔢 ↩️. - 🚶♀️ 🎚, ⚙️
dict
headers
🔢. - 🍪,
dict
cookies
🔢.
🌖 ℹ 🔃 ❔ 🚶♀️ 💽 👩💻 (⚙️ httpx
⚖️ TestClient
) ✅ 🇸🇲 🧾.
Info
🗒 👈 TestClient
📨 💽 👈 💪 🗜 🎻, 🚫 Pydantic 🏷.
🚥 👆 ✔️ Pydantic 🏷 👆 💯 & 👆 💚 📨 🚮 💽 🈸 ⏮️ 🔬, 👆 💪 ⚙️ jsonable_encoder
🔬 🎻 🔗 🔢.
🏃 ⚫️¶
⏮️ 👈, 👆 💪 ❎ pytest
:
$ pip install pytest
---> 100%
⚫️ 🔜 🔍 📁 & 💯 🔁, 🛠️ 👫, & 📄 🏁 🔙 👆.
🏃 💯 ⏮️:
$ pytest
================ test session starts ================
platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
rootdir: /home/user/code/superawesome-cli/app
plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1
collected 6 items
---> 100%
test_main.py <span style="color: green; white-space: pre;">...... [100%]</span>
<span style="color: green;">================= 1 passed in 0.03s =================</span>