FastAPI教程 測試

2021-11-03 14:16 更新

感謝Starlette,測試FastAPI應用程序變得簡單而愉快。

它基于Requests,因此非常熟悉和直觀。

有了它,您可以直接將pytest與FastAPI一起使用。

使用 TestClient

進口TestClient。

創(chuàng)建一個TestClient傳遞給它的FastAPI應用程序。

創(chuàng)建名稱以 開頭的函數test_(這是標準pytest約定)。

TestClient以與使用相同的方式使用對象requests。

assert使用您需要檢查的標準 Python 表達式編寫簡單的語句(再次,標準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"}

提示

請注意,測試功能是正常的def,而不是async def。

而且對客戶端的調用也是普通調用,不是使用await.

這使您可以pytest直接使用而不會出現并發(fā)癥。

技術細節(jié)

您也可以使用from starlette.testclient import TestClient.

FastAPI提供相同starlette.testclient的fastapi.testclient,就像為你的方便,開發(fā)人員。但它直接來自Starlette。

提示

如果async除了向 FastAPI 應用程序發(fā)送請求之外,還想調用測試中的函數(例如異步數據庫函數),請查看高級教程中的異步測試。

分離測試

在實際應用程序中,您可能會將測試放在不同的文件中。

而且您的FastAPI應用程序也可能由多個文件/模塊等組成。

FastAPI應用程序文件

假設您有一個main.py包含FastAPI應用程序的文件:

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def read_main():
    return {"msg": "Hello World"}

測試文件

然后你可以有一個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"}

測試:擴展示例

現在讓我們擴展這個例子并添加更多細節(jié)來看看如何測試不同的部分。

擴展的FastAPI應用程序文件

假設您有一個main_b.py包含FastAPI應用程序的文件。

它有一個GET可能返回錯誤的操作。

它有一個POST可能返回多個錯誤的操作。

兩個路徑操作都需要一個X-Token標頭。

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,您可以像以前一樣使用擴展測試:

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"}

每當您需要客戶端在請求中傳遞信息而您不知道如何傳遞時,您可以在requests.

然后你就在你的測試中做同樣的事情。

例如:

  • 要傳遞路徑或查詢參數,請將其添加到 URL 本身。
  • 要傳遞 JSON 正文,dict請將Python 對象(例如 a )傳遞給參數json。
  • 如果您需要發(fā)送表單數據而不是 JSON,請改用data參數。
  • 要傳遞headers,請dict在headers參數中使用 a 。
  • 對于餅干,一個dict在cookies參數。

有關如何將數據傳遞到后端(使用requests或TestClient)的更多信息,查看請求文檔

信息

請注意,TestClient接收可以轉換為 JSON 的數據,而不是 Pydantic 模型。

如果您的測試中有 Pydantic 模型,并且您想在測試期間將其數據發(fā)送到應用程序,則可以使用JSON Compatible Encoder 中jsonable_encoder描述的。

運行

之后,您只需要安裝pytest:

pip install pytest


████████████████████████████████████████ 100%

它將自動檢測文件和測試,執(zhí)行它們,并將結果報告給您。

運行測試:

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 =================


以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號