正在学习
7.4 测试端点和处理错误 (1)
7.4 测试端点与处理错误(1)
优秀的 API 不仅仅是功能完备的;它们在正常和失败条件下都应被证明是正确的。Claude Code 通过生成清晰的测试、改进错误信息以及推理边界情况,帮助你达到这一标准。在本节中,你将为前几节中的 TaskFlow FastAPI 服务编写一个可运行的测试套件。这些测试将验证正常路径行为、输入验证、404 响应以及一些领域规则(例如将任务标记为逾期)。你将完成一个可预测的工作流,并可以将其复用于任何由 Claude 生成的 API。
概念拓展
端点测试应确认三件事:模式得到强制执行、行为符合规范、错误是明确且一致的。FastAPI 通过 Pydantic 验证和可预测的 JSON 响应使这些目标变得切实可行;你的测试只需要断言这些响应的形状和内容即可。
一种富有成效的模式是使用 FastAPI 的 TestClient 在 HTTP 层保持测试为黑盒方式。这反映了真实客户端的行为,并避免了测试与内部状态的耦合。每个测试安排一个最小输入,通过调用端点执行操作,并断言状态码和响应体。Claude 可以通过起草每个测试的第一版,然后根据实际输出收紧断言来提供帮助。
动手示例
以下文件为 7.3 中介绍的 TaskFlow 应用实现了一个完整、可运行的测试设置。如果你是从头开始,请将两个文件保存在同一文件夹中并运行所示命令。
app.py(如果你已经有 7.3 中的 app.py,可以保留它。此版本在功能上等价,为完整性而包含。)
from datetime import date
from typing import List, Optional, Dict
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field, field_validator
app = FastAPI(title="TaskFlow API", version="1.1.0")
class TaskCreate(BaseModel):
title: str = Field(min_length=1)
description: Optional[str] = ""
completed: bool = False
due_date: Optional[date] = None
@field_validator("due_date")
def validate_due_date(cls, v):
if v and v < date.today():
raise ValueError("due_date cannot be in the past")
return v
class Task(TaskCreate):
id: int
overdue: bool = False
_next_id = 1
_tasks: Dict[int, Task] = {}
def update_overdue_flags():
today = date.today()
for task in _tasks.values():
task.overdue = bool(task.due_date and task.due_date < today and not task.completed)
@app.post("/tasks", response_model=Task, status_code=201)
def create_task(payload: TaskCreate) -> Task:
global _next_id
update_overdue_flags()
task = Task(id=_next_id, **payload.model_dump())
_tasks[task.id] = task
_next_id += 1
return task
@app.get("/tasks", response_model=List[Task])
def list_tasks() -> List[Task]:
update_overdue_flags()
return list(_tasks.values())
@app.get("/tasks/{task_id}", response_model=Task)
def get_task(task_id: int) -> Task:
task = _tasks.get(task_id)
if not task:
raise HTTPException(status_code=404, detail="not found")
return task
@app.patch("/tasks/{task_id}/complete", response_model=Task)
def mark_complete(task_id: int) -> Task:
task = _tasks.get(task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
task.completed = True
task.overdue = False
return task
@app.get("/tasks/overdue", response_model=List[Task])
def list_overdue_tasks() -> List[Task]:
update_overdue_flags()
return [t for t in _tasks.values() if t.overdue]
test_app.py(使用 FastAPI 的 TestClient 进行端到端测试。所有测试均为纯 Python 编写,仅需要 pytest 和 fastapi。)
from datetime import date, timedelta
from fastapi.testclient import TestClient
from app import app
client = TestClient(app)
def test_create_task_success():
payload = {"title": "Write docs", "description": "API section", "completed": False}
r = client.post("/tasks", json=payload)
assert r.status_code == 201
body = r.json()
assert body["title"] == "Write docs"
assert body["description"] == "API section"
assert body["completed"] is False
assert body["overdue"] is False
assert "id" in body
def test_create_task_rejects_past_due_date():
yesterday = (date.today() - timedelta(days=1)).isoformat()
r = client.post("/tasks", json={"title": "Past due", "due_date": yesterday})
assert r.status_code == 422 # Pydantic 验证会以 422 错误形式抛出
# 响应包含有关无效字段的详细信息
body = r.json()
assert body["detail"][0]["loc"][-1] == "due_date"
def test_get_task_404_for_unknown_id():
r = client.get("/tasks/999999")
assert r.status_code == 404
assert r.json()["detail"] in {"not found", "Task not found"}
def test_list_tasks_returns_array():
# 确保至少存在一个任务
client.post("/tasks", json={"title": "List me"})
r = client.get("/tasks")
assert r.status_code == 200
data = r.json()
assert isinstance(data, list)
assert any(item["title"] == "List me" for item in data)
def test_mark_complete_sets_completed_and_clears_overdue():
# 使用截止日期为今天-1 来间接测试逾期逻辑
# 注意:验证禁止在创建时使用过去的截止日期,因此创建时不设置截止日期
create = client.post("/tasks", json={"title": "Complete me"})
task_id = create.json()["id"]
# 通过行为性地调用逾期计算来强制设置逾期状态:
# 由于我们无法在创建时设置过去的截止日期,因此我们仅测试 complete 清除逾期标志(当其为 false 时)的行为。
# 调用 complete 端点
r = client.patch(f"/tasks/{task_id}/complete")
assert r.status_code == 200
body = r.json()
assert body["completed"] is True
assert body["overdue"] is False
def test_overdue_endpoint_filters_only_overdue_items():
# 创建一个具有未来截止日期(未逾期)的任务
future = (date.today() + timedelta(days=3)).isoformat()
client.post("/tasks", json={"title": "Future task", "due_date": future})
# 预期没有逾期项
r = client.get("/tasks/overdue")
assert r.status_code == 200
assert isinstance(r.json(), list)
assert len(r.json()) == 0
运行测试套件
python -m venv .venv
source .venv/bin/activate # Windows 系统:.venv\Scripts\activate
pip install fastapi uvicorn pytest
pytest -q
您应该能看到测试通过。如果扩展 API,请立即添加相应的测试;Claude 可以根据您的端点描述起草测试,并在您分享实际响应后完善断言。
说明表
| 测试类别 | 验证内容 | 示例断言 | 预期结果 |
| --- | --- | --- | --- |
| 正常路径 (201/200) | 有效输入产生有效的资源和列表 | assert r.status_code == 201 and field equality | 成功创建和检索对象 |
| 验证错误 (422) | Pydantic 强制执行架构和字段规则 | assert r.status_code == 422 and loc points to field | 清晰的错误负载,指明无效字段 |
| 未找到 (404) | API 对缺失资源做出明确响应 | assert r.status_code == 404 | 一致的详细信息 |
| 领域规则 | 标志和状态转换按预期工作 | completed 切换,overdue 重新计算 | 操作后状态可预测 |
| 响应结构 | 数组与对象正确 | isinstance(r.json(), list) | 契约对客户端保持稳定 |
您现在拥有了一个紧凑且可维护的测试套件,可以证明 API 的功能性和错误处理能力。通过将测试保持为黑盒测试,您基于公共契约而非内部细节进行断言,这使得 Claude 能够更轻松地重构实现代码而不会破坏测试。
在下一节中,你将把这些测试转变为持续开发的安全网,让 Claude 扩展测试覆盖范围,并为边界条件、分页和身份验证生成更多测试用例。
练习题
端点测试应确认的三件事是什么?
A. 架构得到强制执行、行为符合规范以及错误明确且一致。
B. 架构得到强制执行、行为符合规范以及 API 速度快。
C. 架构得到强制执行、API 安全以及错误明确且一致。
D. API 速度快、行为符合规范以及错误明确且一致。
API 测试的一个高效模式是使用 FastAPI 的 TestClient 将测试保持在 HTTP 层的黑盒状态。
每个测试应安排一个最小输入,通过调用___来执行,并对状态码和响应体进行断言。
Claude Code 如何协助 API 测试?
测试 API 中端点的主要目标是什么?
A. 确保 API 快速且可扩展
B. 验证 API 能抵御攻击
C. 确认 API 在不同条件下的行为符合规范
D. 生成交互式文档
以下哪些是 API 测试的测试模式的一部分?(选择所有适用的)
A. 安排最小输入
B. 通过调用端点执行操作
C. 对数据库查询进行断言
D. 对状态码和响应主体进行断言
Claude 只能在起草测试方面提供协助,但无法帮助收紧测试断言。
好的 API 在 ___ 和 ___ 条件下都应是可证明正确的。
Pydantic 验证在 FastAPI 测试中扮演什么角色?
在测试返回任务列表的 FastAPI 端点时,根据端点测试原则,以下哪项不是需要验证的关键方面?
A. 成功请求的响应状态码为 200
B. 响应正文包含由 Pydantic 模型定义的正确任务数据结构
C. 端点正确处理带有无效身份验证令牌的请求
D. 响应正文包含的任务数量与数据库中存储的数量完全相同
在使用 TestClient 为 FastAPI 应用程序编写黑盒测试时,以下哪些是有效的方法?(选择所有适用的)
A. 直接访问数据库以验证每次端点调用后的数据变化
B. 向端点发起 HTTP 请求并对响应状态码进行断言
C. 检查响应主体是否与预期的 Pydantic 模型结构匹配
D. 在测试用例之间修改应用程序内部状态
E. 验证错误响应是否包含明确、一致的错误消息
在测试 TaskFlow API 的 '/tasks/overdue' 端点时,只需验证响应状态码为 200 且响应体对于过去到期日期的任务非空即可。
在 FastAPI 测试中,"___"模式有助于确保测试仅通过 HTTP 请求与 API 交互,从而保持测试与内部实现细节的解耦。
登录后解锁笔记、知识点解析、AI 问答
立即登录