Skip to content

确保质量: 测试

可靠性是专业软件工程的基石。即使最快的 API,如果它返回不正确的数据或意外崩溃,也毫无用处。在本章中,我们将探讨如何为你的 FastAPI 应用程序编写自动化测试,确保你的代码在投入生产之前完全符合预期。

FastAPI 站在巨人的肩膀上,在测试方面,它利用了 Starlette 的强大功能。我们将使用 TestClient,这是一个允许你模拟对应用程序进行 HTTP 请求而无需实际运行服务器的工具。它直接与你的 FastAPI 应用程序对象进行交互。

对于测试运行器,Python 生态系统中的行业标准是 pytest。它使用简单但功能强大。

首先,你需要安装 pytest 和 httpx。TestClient 在底层使用 httpx 来执行请求。

Terminal window
pip install pytest httpx

假设你有一个简单的 main.py 文件。

main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"msg": "Hello World"}

为了测试它,创建一个名为 test_main.py 的文件。在这个文件中,我们导入 TestClient 和我们的 app。

test_main.py
from fastapi.testclient import TestClient
from .main import app
client = TestClient(app)
def test_read_root():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"msg": "Hello World"}

要运行测试,只需在终端中执行 pytest。pytest 会自动检测以 test_ 开头的文件和以 test_ 开头的函数。

测试不仅仅是关于“成功路径”(一切顺利的情况)。同样重要的是要确保当给定无效输入时,你的 API 能够优雅地失败。由于 FastAPI 使用 Pydantic 自动验证数据,我们可以测试这些验证错误是否按预期发生。

main.py
@app.get("/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id}
# test_main.py
def test_read_item_bad_type():
# We send a string "foo", but the API expects an integer
# 我们发送字符串 "foo",但 API 期望一个整数
response = client.get("/items/foo")
assert response.status_code == 422
# We can even check the specific error message structure
# 我们甚至可以检查特定的错误消息结构
assert response.json()["detail"][0]["msg"] == "Input should be a valid integer"

FastAPI 测试系统最强大的功能之一是能够覆盖依赖项(override dependencies)。当你的应用程序与外部资源(如数据库、身份验证提供程序或电子邮件服务)交互时,这一点至关重要。

你通常不希望你的测试修改生产数据库或发送真实的电子邮件。FastAPI 允许你使用 app.dependency_overrides 将真实的依赖项替换为“模拟”(mock)或测试版本。

假设你有一个验证 token 的依赖项:

# main.py dependency
async def verify_token(x_token: str = Header(...)):
if x_token != "super-secret-token":
raise HTTPException(status_code=400, detail="Invalid X-Token header")
return x_token

在你的测试中,你可以通过覆盖它来绕过检查特定字符串的逻辑。

test_main.py
from fastapi.testclient import TestClient
from .main import app, verify_token
client = TestClient(app)
async def override_verify_token():
# We simply return a value, bypassing the check
# 我们只是简单地返回一个值,绕过了检查
return "test-token"
# Apply the override
# 应用覆盖
app.dependency_overrides[verify_token] = override_verify_token
def test_secure_endpoint():
response = client.get("/secure-data")
assert response.status_code == 200
# Clean up the override after the test so other tests aren't affected
# 测试后清除覆盖,以免影响其他测试
app.dependency_overrides = {}

这项技术被广泛用于将真实的 SQL 数据库会话替换为临时的、内存中的 SQLite 数据库进行测试,从而确保你的测试快速、隔离且安全。