11 · 后端测试实战:单元 / 接口 / 集成
前端测 UI,后端测逻辑与接口契约。本篇用 pytest 讲清后端的测试金字塔,以及接口测试为什么是后端最重要的测试。
速览卡
| 项 | 内容 |
|---|---|
| 一句话定位 | 后端怎么分层测试:单元测逻辑、接口测契约、集成测链路 |
| 核心内容 | 测试金字塔在后端、pytest 基础、FastAPI TestClient、Mock 与真实依赖、测试数据库、CI 里跑测试 |
| 读完能回答 | 后端最重要的测试是什么?测试数据库怎么搭?第三方服务(LLM/支付)在测试里怎么办? |
| 相关板块 | C09 · 服务端语言上手 | C10 · 数据建模 | C17 · 部署与 CI/CD |
一、测试金字塔在后端
| 层级 | 测什么 | 数量 | 速度 |
|---|---|---|---|
| 单元测试 | 纯函数、业务规则、边界 | 多 | 快 |
| 接口测试 | 路由 + 校验 + 状态码 + 错误体 | 最多 | 中 |
| 集成测试 | 数据库、缓存、外部服务整条链路 | 少 | 慢 |
后端最重要的测试是接口测试:因为后端的一切能力最终通过 API 暴露,接口测试同时覆盖了路由、校验、错误处理和一部分逻辑。
二、pytest 基础
python
# test_models.py
from models import calc_discount
def test_discount_boundary():
assert calc_discount(price=100, rate=0.8) == 80
def test_discount_rejects_zero():
assert calc_discount(price=100, rate=0) is Nonebash
pytest -q前端 Jest 是
describe/it/expect,pytest 是def test_xxx+assert。命名带test_即被收集。
三、接口测试:FastAPI TestClient
python
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_create_item():
resp = client.post("/items", json={"name": "咖啡", "price": 29.9})
assert resp.status_code == 201
assert resp.json()["name"] == "咖啡"
def test_create_item_invalid():
resp = client.post("/items", json={"name": "", "price": -1})
assert resp.status_code == 422 # Pydantic 校验失败的默认码接口测试优先断言行为契约:状态码、返回结构、错误体。别断言实现细节(如具体某行代码)。
四、Mock 与真实依赖
后端会依赖数据库、Redis、第三方 API(含 LLM)。原则是测试越靠下,越要真:
| 依赖 | 测试里的处理 |
|---|---|
| 自己的数据库 | 用独立测试库,别碰开发库 |
| Redis | 内存版或独立实例 |
| 第三方 API / LLM | Mock 掉(返回固定响应) |
| 定时任务/消息队列 | 测试里改为同步直接调 handler |
python
from unittest.mock import patch
@patch("services.llm.call_llm")
def test_chat(mock_call):
mock_call.return_value = {"content": "固定的回答"}
resp = client.post("/chat", json={"msg": "hi"})
assert resp.json()["content"] == "固定的回答"为什么要 Mock LLM:真实调用慢、花钱、结果不稳定(非确定性),会污染测试。把"我调没调对 API"和"我的逻辑对不对"分开测。
五、测试数据库
python
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Base
@pytest.fixture
def db():
engine = create_engine("sqlite:///:memory:") # 或独立测试库
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
yield session
session.close()集成测试套路:建 schema → 灌种子数据 → 跑断言 → 清空。不要让测试互相依赖顺序。
六、在 CI 里跑测试
见 C17 · 部署与 CI/CD 的 CI 章节,核心是:
yaml
# 伪代码
steps:
- install python + deps
- run pytest # 失败即阻断合并
- run build + lint原则:测试不通过,代码不能合并、不能上线。这是后端和前端一样要守的底线。
自检清单
- [ ] 能说清后端测试金字塔和"接口测试最重要"的理由
- [ ] 会用 pytest 写单元测试和参数化用例
- [ ] 会用 TestClient 写接口测试,并断言状态码与错误体
- [ ] 知道什么时候 Mock(第三方/LLM)、什么时候用真实依赖(自己数据库)
- [ ] 会用独立测试库做集成测试,不让测试互相依赖
常见误区
- 误区:只测工具函数,不测接口。 后端核心是接口契约,接口测试才是大头。
- 误区:把 Mock 当万能。 Mock 掉数据库会让集成测试失去意义;原则是"自己的依赖用真的,第三方的才 Mock"。
- 误区:测试碰生产/开发库。 永远用独立测试库,否则数据会被污染。
- 误区:测实现细节。 测"行为"不测"实现",否则重构一次挂一片。
动手练习
练习 A:给 Todo API 补接口测试
把 C09 的 Todo API 补上:正常创建、非法输入(422)、删除不存在(404)三个接口测试。
练习 B:Mock 一个 LLM 调用
给你的 AI 接口用 @patch Mock 掉 LLM,验证"入参拼得对、返回结构对"。
练习 C:测试库改造
把 C10 的数据访问层接入独立测试库,写一个依赖真实数据库的集成测试。
相关文档
本区
- 09 · 服务端语言上手 —— 被测对象从这来
- 10 · 数据建模与数据访问 —— 数据层测试的依赖
- 12 · 错误处理日志配置 —— 错误体怎么设计才能测
- 17 · 部署与 CI/CD —— 测试进 CI
其他域
- 19 · 测试实战(Flutter) —— 前端侧测试体系对照
- 11 · 前端测试策略 —— 前端测试金字塔