正在学习

内存存储

7.4 测试端点和处理错误

好的 API 不仅仅是功能完整;它们在正常和失败条件下都应该是可证明正确的。Claude Code 通过生成清晰的测试、改进错误消息以及推理边界情况,帮助你达到这个标准。在本节中,你将为前几节中的 TaskFlow FastAPI 服务编写一个可运行的测试套件。这些测试将验证正常路径行为、输入验证、404 响应以及一些领域规则(例如将任务标记为逾期)。你将完成一个可预测的工作流,可以重用于任何 Claude 生成的 API。

概念阐述

端点测试应确认三件事:强制执行 schema、行为与规范匹配、错误明确且一致。FastAPI 通过 Pydantic 验证和可预测的 JSON 响应使这些目标变得实用;你的测试只需要断言这些响应的形状和内容。

一个有效的模式是在 HTTP 层保持测试黑盒,使用 FastAPI 的 TestClient。这反映了真实的客户端行为,并避免了将测试与内部状态耦合。每个测试都安排一个最小输入,通过调用端点执行操作,并断言状态码和响应体。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 的任务,以间接测试逾期逻辑
    # 注意:创建时校验禁止使用过去的 due_date,因此创建时不含 due_date
    create = client.post("/tasks", json={"title": "Complete me"})
    task_id = create.json()["id"]
    # 通过行为方式强制设置逾期状态:
    # 由于创建时无法设置过去的 due_date,我们只测试当 overdue 为 false 时,complete 接口会清除该标志。
    # 调用 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():
    # 创建一个 due_date 为未来某天的任务(不会逾期)
    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 字段相等 | 成功创建并检索对象 |
| 校验错误 (422) | Pydantic 强制执行 schema 和字段规则 | assert r.status_code == 422 且 loc 指向字段 | 错误负载清晰指出无效字段 |
| 未找到 (404) | API 对缺失资源给出明确响应 | assert r.status_code == 404 | detail 消息保持一致 |
| 领域规则 | 标志位和状态转换符合预期 | completed 切换、overdue 重新计算 | 操作后状态可预测 |
| 响应结构 | 数组与对象类型正确 | isinstance(r.json(), list) | 对客户端的契约保持稳定 |

现在你拥有了一个紧凑且易于维护的测试套件,既能证明功能正确性,也能验证错误处理。通过保持黑盒测试,你针对的是公共契约而非内部实现细节,这让 Claude 可以更轻松地重构实现代码而不破坏测试。

在下一节中,你将这些测试转变为持续开发的安全网,要求 Claude 扩展测试覆盖范围,并为边界条件、分页和身份验证生成额外的测试用例。

练习题

API开发中端点测试的主要目标是什么?

A. 确保API功能完整
B. 确认架构被强制执行、行为符合规范、错误明确
C. 测试API的内部状态
D. 验证API在高负载下的性能

以下哪些是使用 FastAPI 进行 API 测试的好处?(选择所有适用的)

A. 自动生成交互式文档
B. Pydantic 验证实现模式强制约束
C. 可预测的 JSON 响应使测试更轻松
D. 内置支持数据库迁移

判断题:端点测试应该与 API 的内部状态耦合,以确保全面覆盖。

API 端点的测试模式包括安排___、通过调用端点来执行,以及对状态码和响应体进行断言。

Claude如何在API测试过程中提供帮助?

以下哪一项不是 API 测试的推荐做法?

A. 测试正常路径和失败条件
B. 编写依赖于内部实现细节的测试
C. 使用 Pydantic 模型进行模式验证
D. 对状态码和响应主体都进行断言

测试应验证API行为的哪些方面?(选择所有适用的)

A. 输入验证
B. 数据库架构迁移
C. 错误处理和明确的错误消息
D. 特定于领域的规则(例如,将任务标记为逾期)

判断题:好的 API 只需要在正常条件下正确,无需在失败条件下正确。

FastAPI 的 ___ 让编写用于断言响应形状和内容的测试变得切实可行。

解释 "arrange-act-assert" 模式在 API 测试中的作用。

在测试"获取任务端点"(kp_1_1_5)时,对于有效的任务ID,应该期望哪个HTTP状态码?

A. 200 OK
B. 201 Created
C. 404 Not Found
D. 500 Internal Server Error

哪些前面章节中的端点应该测试输入验证?(选择所有适用的)

A. 创建任务端点(kp_1_1_3
B. 列出任务端点(kp_1_1_4
C. 修补任务端点(kp_1_1_8
D. 健康检查端点(kp_1_1_2

判断题:'删除任务端点'(kp_1_1_9) 应该在删除后在响应体中返回一个任务对象。

当测试 'Patch Task Endpoint'(kp_1_1_8)时,响应主体应在部分更新后匹配 ___ 模型。

测试应如何验证"列出逾期任务端点"(kp_7_3_005)的行为?

在 FastAPI 中,推荐使用哪个工具进行 HTTP 层的黑盒测试?

A. pytest
B. unittest
C. FastAPI 的 TestClient
D. Selenium

在调用"创建任务端点"(kp_1_1_3)时,测试应该断言什么?(选择所有适用的选项)

A. 状态码为
B. 响应体匹配 模型
C. 任务存储在内存中
D. 任务的 已递增

判断题:'健康检查端点' (kp_1_1_2) 在测试时需要进行身份验证。

在测试"更新任务端点"(kp_1_1_6)时,状态码表示任务___不存在。

测试如何验证"标记完成端点"(kp_7_3_005)正确更新了 标志?

关于使用 TestClient 测试 FastAPI 应用程序,以下哪些说法是正确的?(选择所有适用的)

A. 测试应在 HTTP 层进行黑盒测试
B. 测试应与应用程序的内部状态紧密耦合
C. 每个测试应安排最小输入,通过调用端点来执行,并断言状态码和响应体
D. 测试应验证应用程序的数据库架构

在测试 TaskFlow API 列出逾期任务的端点时,测试应断言响应主体包含 为 ___ 且 标志为 ___ 的任务。

解释 Claude 如何协助为 FastAPI 应用程序编写测试,以及它可以在测试的哪些方面提供帮助。

登录后解锁笔记、知识点解析、AI 问答

立即登录