感謝Starlette,測(cè)試FastAPI應(yīng)用程序變得簡(jiǎn)單而愉快。
它基于Requests,因此非常熟悉和直觀。
有了它,您可以直接將pytest與FastAPI一起使用。
進(jìn)口TestClient。
創(chuàng)建一個(gè)TestClient傳遞給它的FastAPI應(yīng)用程序。
創(chuàng)建名稱以 開頭的函數(shù)test_(這是標(biāo)準(zhǔn)pytest約定)。
TestClient以與使用相同的方式使用對(duì)象requests。
assert使用您需要檢查的標(biāo)準(zhǔn) Python 表達(dá)式編寫簡(jiǎn)單的語句(再次,標(biāo)準(zhǔn)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"}
提示
請(qǐng)注意,測(cè)試功能是正常的def,而不是async def。
而且對(duì)客戶端的調(diào)用也是普通調(diào)用,不是使用await.
這使您可以pytest直接使用而不會(huì)出現(xiàn)并發(fā)癥。
技術(shù)細(xì)節(jié)
您也可以使用from starlette.testclient import TestClient.
FastAPI提供相同starlette.testclient的fastapi.testclient,就像為你的方便,開發(fā)人員。但它直接來自Starlette。
提示
如果async除了向 FastAPI 應(yīng)用程序發(fā)送請(qǐng)求之外,還想調(diào)用測(cè)試中的函數(shù)(例如異步數(shù)據(jù)庫函數(shù)),請(qǐng)查看高級(jí)教程中的異步測(cè)試。
在實(shí)際應(yīng)用程序中,您可能會(huì)將測(cè)試放在不同的文件中。
而且您的FastAPI應(yīng)用程序也可能由多個(gè)文件/模塊等組成。
假設(shè)您有一個(gè)main.py包含F(xiàn)astAPI應(yīng)用程序的文件:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_main():
return {"msg": "Hello World"}
然后你可以有一個(gè)test_main.py包含你的測(cè)試的文件,并app從main模塊 ( main.py)導(dǎo)入你的:
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"}
現(xiàn)在讓我們擴(kuò)展這個(gè)例子并添加更多細(xì)節(jié)來看看如何測(cè)試不同的部分。
假設(shè)您有一個(gè)main_b.py包含F(xiàn)astAPI應(yīng)用程序的文件。
它有一個(gè)GET可能返回錯(cuò)誤的操作。
它有一個(gè)POST可能返回多個(gè)錯(cuò)誤的操作。
兩個(gè)路徑操作都需要一個(gè)X-Token標(biāo)頭。
from typing import Optional
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: Optional[str] = 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=400, detail="Item already exists")
fake_db[item.id] = item
return item
然后test_main_b.py,您可以像以前一樣使用擴(kuò)展測(cè)試:
from fastapi.testclient import TestClient
from .main_b 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 == 400
assert response.json() == {"detail": "Item already exists"}
每當(dāng)您需要客戶端在請(qǐng)求中傳遞信息而您不知道如何傳遞時(shí),您可以在requests.
然后你就在你的測(cè)試中做同樣的事情。
例如:
有關(guān)如何將數(shù)據(jù)傳遞到后端(使用requests或TestClient)的更多信息,請(qǐng)查看請(qǐng)求文檔。
信息
請(qǐng)注意,TestClient接收可以轉(zhuǎn)換為 JSON 的數(shù)據(jù),而不是 Pydantic 模型。
如果您的測(cè)試中有 Pydantic 模型,并且您想在測(cè)試期間將其數(shù)據(jù)發(fā)送到應(yīng)用程序,則可以使用JSON Compatible Encoder 中jsonable_encoder描述的。
之后,您只需要安裝pytest:
pip install pytest
████████████████████████████████████████ 100%
它將自動(dòng)檢測(cè)文件和測(cè)試,執(zhí)行它們,并將結(jié)果報(bào)告給您。
運(yùn)行測(cè)試:
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 ...... [100%]
================= 1 passed in 0.03s =================
更多建議: