Skip to content

接口测试

手动用浏览器或 curl 测接口,改一次代码测一遍——接口多了根本测不过来。自动化测试是写一遍测试代码,之后每次改代码跑一下,几秒钟确认所有接口还没坏。FastAPI 自带测试工具,不用启动真实服务器。

一、pytest 基础

Python 最主流的测试框架是 pytest。写一个函数、用 assert 断言、pytest 自动发现和执行:

bash
uv add pytest
python
# test_math.py
def test_add():
    assert 1 + 1 == 2


def test_string_upper():
    assert "hello".upper() == "HELLO"

运行:

bash
uv run pytest

pytest 自动找以 test_ 开头的文件和函数,运行里面的 assert。断言失败显示具体哪一行、预期值和实际值。

二、fixture——测试前准备、测试后清理

测试接口需要数据库连接、测试数据、清理数据。每次手写太重复。fixture 是 pytest 的依赖注入机制——写一次,多个测试复用:

python
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Base, Article


@pytest.fixture
def db_session():
    """每个测试用独立的内存数据库,测试完自动销毁。"""
    # 用 SQLite 内存数据库,不碰真实数据库文件
    engine = create_engine("sqlite:///:memory:")
    Base.metadata.create_all(engine)

    Session = sessionmaker(bind=engine)
    session = Session()

    yield session  # 把 session 交给测试函数用

    # 测试结束后清理
    session.close()


def test_create_article(db_session):
    """测试创建文章。db_session 参数自动注入 fixture 返回的 session。"""
    article = Article(title="测试文章", content="内容", status="published")
    db_session.add(article)
    db_session.commit()

    # 断言:数据库里确实有一条
    articles = db_session.query(Article).all()
    assert len(articles) == 1
    assert articles[0].title == "测试文章"

@pytest.fixture 标注的函数是 fixture。测试函数的参数名跟 fixture 名字一样(db_session),pytest 自动注入。yield 前面的代码是准备,yield 后面的代码是清理——每个测试拿到全新的内存数据库,互不干扰。

sqlite:///:memory: 是 SQLite 内存模式——数据库存在内存里,不写文件,连接关闭就消失。用它做测试数据库,速度快且不污染开发库

三、TestClient——测 FastAPI 接口

FastAPI 基于 Starlette,自带 TestClient——不用启动 uvicorn,直接在进程内调用 app:

python
from fastapi.testclient import TestClient
from main import app

client = TestClient(app)


def test_list_articles():
    """测试 GET /articles。"""
    response = client.get("/articles")

    assert response.status_code == 200
    data = response.json()
    assert "items" in data


def test_create_article():
    """测试 POST /articles。"""
    response = client.post(
        "/articles",
        json={"title": "新文章", "content": "正文内容"},
    )

    assert response.status_code == 200
    assert response.json()["title"] == "新文章"


def test_get_article_not_found():
    """测试查不存在的文章返回 404。"""
    response = client.get("/articles/999")

    assert response.status_code == 404

TestClient(app) 包装了 FastAPI app,client.get("/articles") 就像发了一个真实的 HTTP 请求,但不走网络——直接在内存里调用路由函数。响应的 status_codejson() 跟真实请求完全一样。

测试正常流程(创建成功)、异常流程(查不到返回 404)、边界条件(参数格式错误返回 422)——这三种测全了,接口的基本质量就有保证。

四、测试需要登录的接口

需要 Token 的接口,测试时要带认证。两种做法:

1 先登录拿 Token

python
def test_get_profile():
    """测试需要登录的接口。"""
    # 先注册并登录
    client.post("/register", json={"username": "testuser", "password": "testpass"})

    login_resp = client.post(
        "/login",
        data={"username": "testuser", "password": "testpass"},
    )
    token = login_resp.json()["access_token"]

    # 带 Token 访问需要认证的接口
    response = client.get(
        "/me",
        headers={"Authorization": f"Bearer {token}"},
    )

    assert response.status_code == 200
    assert response.json()["username"] == "testuser"

2 用 fixture 直接签发 Token

如果登录逻辑复杂,测试时直接签发 Token 跳过登录:

python
@pytest.fixture
def auth_headers():
    """直接签发一个测试用 Token。"""
    from auth import create_token

    token = create_token({"sub": "1", "username": "testuser"})
    return {"Authorization": f"Bearer {token}"}


def test_create_article_authed(auth_headers):
    """测试登录后创建文章。"""
    response = client.post(
        "/articles",
        json={"title": "测试", "content": "内容"},
        headers=auth_headers,
    )

    assert response.status_code == 200

fixture 直接签发 Token 更快,不用走完整登录流程。但要确保 create_token 函数的签名不变,否则 fixture 和真实逻辑会脱节。

五、测试要测什么

类型测什么示例
正常流程预期操作返回预期结果创建文章 → 200,数据库有记录
异常流程错误操作返回正确错误码查不存在的 ID → 404
参数校验参数格式错误被拦截标题为空 → 422
权限控制未登录或权限不足被拒绝不带 Token → 401
边界条件边界值处理正确分页 limit=0 或 limit=999999

核心原则:每个接口至少测正常流程和异常流程各一条。不追求 100% 覆盖率,但关键接口(认证、增删改)的测试不能少。

六、运行测试

bash
# 运行所有测试
uv run pytest

# 只看结果,不显示详细输出
uv run pytest -q

# 测试某个文件
uv run pytest test_articles.py

# 测试某个函数
uv run pytest test_articles.py::test_create_article

# 显示 print 输出(调试时用)
uv run pytest -s

CI/CD 流水线里加一步 uv run pytest——代码推上去自动跑测试,测试不过不让部署。这是持续集成的基本保障