๐ฌ¶
๐ ๐, ๐ฌ 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>