Skip to content

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 None
bash
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 / LLMMock 掉(返回固定响应)
定时任务/消息队列测试里改为同步直接调 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 的数据访问层接入独立测试库,写一个依赖真实数据库的集成测试。


相关文档 ​

本区 ​

其他域 ​