前端转 AI 100 天 Day 24:API 测试——用 httpx + pytest 测 FastAPI

前端转 AI 第 24 天:API 测试 - 用 httpx + pytest 测 FastAPI

昨日回顾

Day 23 把数据库层升级到生产级:请求级 Session、Alembic 迁移、连接池。

但有个问题 ------API 层一行测试都没有。

Day 18-19 测的是业务层(storage_orm.py / memory.py),API 层是"接口契约",最容易出问题:

  • 改了参数名,前端不知道
  • 错误格式变了,前端处理崩了
  • 加了认证,忘了某个接口也要加

今天给 API 写一套完整测试。

Day 24 目标:掌握 FastAPI 测试的三件套(TestClient / 依赖覆盖 / 临时数据库),给 API 全链路测试。


一、为什么 API 测试和单元测试不一样

单元测试 :测函数逻辑,隔离一切。

API 测试 :测"请求进来,响应出去",中间尽量真实。

关注点 单元测试 API 测试
测试对象 函数 / 类 HTTP 端点
输入 参数 HTTP 请求(URL / Header / Body)
输出 返回值 HTTP 响应(状态码 / Body / Header)
关注点 逻辑正确 契约正确 + 状态码正确
Mock 程度 全 mock 只 mock 外部(LLM / 第三方)
数据库 mock 用真数据库(临时)

API 测试是"集成测试"------从请求到响应,中间所有层都跑一遍。

为什么必须做?

  • 契约是第一位的 :前端不看你源码,只看 /docs。改了字段名,前端不知道
  • 状态码是契约的一部分:404 / 422 / 500 前端要分别处理
  • 认证 / 中间件是横切逻辑:容易漏掉某个接口
  • 比手动测试靠谱:Postman 会忘记、会漏,代码不会

二、FastAPI 测试三件套

FastAPI 官方推荐用 TestClient,我们再加两个关键工具。

1. TestClient:同步客户端

安装:

bash 复制代码
pip install httpx

FastAPI 的 TestClient 基于 httpx,所以要装它。

写法:

python 复制代码
from fastapi.testclient import TestClient
from myapp.main import app


client = TestClient(app)


def test_health():
    response = client.get("/health")
    assert response.status_code == 200
    assert response.json() == {"status": "ok"}

看起来就像发真实请求,但不会启动服务器------FastAPI 拦截请求,直接调内部逻辑。

2. AsyncClient:异步客户端

要测异步接口时用:

python 复制代码
import pytest
from httpx import AsyncClient, ASGITransport
from myapp.main import app


@pytest.mark.asyncio
async def test_health_async():
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.get("/health")
        assert response.status_code == 200

区别:

类型 用法 适合
TestClient 同步,简单 90% 场景
AsyncClient 异步,await 需要测并发 / 原生异步

建议:先用 TestClient ,够用。有明确异步需求再换 AsyncClient。

3. 依赖覆盖:dependency_overrides

最关键的技能。 测试时把"真实依赖"换成"测试依赖"。

python 复制代码
def get_db():
    return RealDatabase()   # 生产用的


def get_fake_db():
    return FakeDatabase()   # 测试用的


# 测试时替换
app.dependency_overrides[get_db] = get_fake_db

这让你能:

  • 不污染生产数据库:用临时数据库
  • 不真调 LLM:用 Mock 客户端
  • 不需要环境变量:认证直接通过

用法:

python 复制代码
@pytest.fixture
def client():
    app.dependency_overrides[get_db] = get_test_db
    with TestClient(app) as c:
        yield c
    app.dependency_overrides.clear()   # 清理,避免污染其他测试

这个 fixture 是 API 测试的基础。


三、测试项目结构

bash 复制代码
cp -r day23/ai-assistant-api-v3 day24/ai-assistant-api-v4
cd day24/ai-assistant-api-v4

新增测试文件:

bash 复制代码
tests/
├── conftest.py             # 已有(加 API 相关 fixture)
├── test_storage.py         # 已有
├── test_memory.py          # 已有
├── api/                    # ← 新增
│   ├── __init__.py
│   ├── conftest.py         # API 专用 fixture
│   ├── test_health.py      # 健康检查
│   ├── test_chat.py        # 对话接口
│   ├── test_sessions.py    # 会话管理
│   └── test_auth.py        # 认证

为什么单独建 api/ 目录?

  • 隔离:API 测试的 fixture 和业务测试不同
  • 组织:测试文件多了要分类
  • 独立运行 :pytest tests/api/ 只跑 API 测试

四、API 测试 fixture

核心:conftest.py 里的"测试客户端"fixture。

1. tests/api/conftest.py

python 复制代码
"""API 测试专用 fixture"""
import pytest
from pathlib import Path
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session

from ai_assistant.api.app import app
from ai_assistant.api.deps import (
    get_database, get_db_session, get_llm, get_memory,
)
from ai_assistant.api.auth import verify_api_key
from ai_assistant.db_orm import DatabaseORM
from ai_assistant.orm_models import Base
from ai_assistant.llm import MockLLMClient
from ai_assistant.memory import MemoryManager


@pytest.fixture
def test_db(tmp_path):
    """测试用的数据库(独立文件)"""
    db_path = tmp_path / "test.db"
    db = DatabaseORM.__new__(DatabaseORM)   # 不走 __init__ 的默认路径
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker
    from sqlalchemy.pool import StaticPool

    db.engine = create_engine(
        f"sqlite:///{db_path}",
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,
    )
    db.SessionLocal = sessionmaker(bind=db.engine, expire_on_commit=False)
    Base.metadata.create_all(db.engine)

    yield db
    db.close()


@pytest.fixture
def test_session(test_db):
    """请求级 Session(模拟 get_db_session)"""
    session = test_db.SessionLocal()
    try:
        yield session
    finally:
        session.close()


@pytest.fixture
def fake_llm():
    """假的 LLM 客户端"""
    llm = MockLLMClient(delay=0)   # 无延迟,测试快
    return llm


@pytest.fixture
def client(test_db, fake_llm):
    """测试客户端(覆盖所有依赖)"""
    from sqlalchemy.orm import sessionmaker

    # 1. 覆盖数据库
    def override_get_database():
        return test_db

    def override_get_db_session():
        session = test_db.SessionLocal()
        try:
            yield session
            session.commit()
        except Exception:
            session.rollback()
            raise
        finally:
            session.close()

    def override_get_llm():
        return fake_llm

    def override_get_memory():
        return MemoryManager(
            max_context_tokens=8000,
            recent_window=10,
            summary_threshold=20,
        )

    def override_verify_api_key():
        """跳过认证"""
        return "test-key"

    app.dependency_overrides[get_database] = override_get_database
    app.dependency_overrides[get_db_session] = override_get_db_session
    app.dependency_overrides[get_llm] = override_get_llm
    app.dependency_overrides[get_memory] = override_get_memory
    app.dependency_overrides[verify_api_key] = override_verify_api_key

    with TestClient(app) as c:
        yield c

    # 清理
    app.dependency_overrides.clear()

逐块拆解:

1. test_db fixture

每次测试生成独立的数据库文件 (tmp_path 下),测完自动删除。

关键技巧:DatabaseORM.__new__(DatabaseORM)

不走 __init__ (否则会连到 data/assistant.db),直接构造一个"空壳",然后手动设置 engine 和 SessionLocal。

2. test_session fixture

模拟请求级 Session------测试里需要直接操作数据库时用。

3. fake_llm fixture

用 MockLLMClient(delay=0)------Day 14 写的假客户端,测试里正好用上。

延迟设为 0 让测试跑得飞快。

4. client fixture

核心------覆盖所有依赖:

  • get_database → 测试数据库
  • get_db_session → 测试 Session
  • get_llm → 假 LLM
  • get_memory → 测试用 MemoryManager
  • verify_api_key → 跳过认证

with TestClient(app) as c:确保 lifespan 正确执行。

app.dependency_overrides.clear():每个测试后清理,避免污染。

2. 为什么用 dependency_overrides 而不是 monkeypatch

monkeypatch 也行,但 dependency_overrides 更"官方":

方式 优点 缺点
monkeypatch 通用 侵入性强,容易漏
dependency_overrides FastAPI 官方 只对 Depends 生效

FastAPI 官方推荐用 dependency_overrides------它精确、可控、清理干净。


五、写第一个 API 测试

1. tests/api/test_health.py

python 复制代码
"""健康检查测试"""


class TestHealth:
    def test_health_returns_ok(self, client):
        response = client.get("/health")
        assert response.status_code == 200
        assert response.json() == {"status": "ok"}

    def test_health_has_request_id(self, client):
        response = client.get("/health")
        # 中间件加的
        assert "x-request-id" in response.headers

    def test_health_has_process_time(self, client):
        response = client.get("/health")
        assert "x-process-time" in response.headers
        assert float(response.headers["x-process-time"]) >= 0

这几个测试验证了 Day 22 的中间件生效了。 别小看它------中间件最容易"改坏而不自知"。

2. tests/api/test_chat.py

python 复制代码
"""对话接口测试"""
import pytest


class TestChat:
    def test_chat_creates_new_session(self, client):
        """不传 session_id,自动创建新会话"""
        response = client.post("/api/chat", json={"message": "你好"})

        assert response.status_code == 200
        data = response.json()

        assert "session_id" in data
        assert len(data["session_id"]) == 8
        assert data["message_count"] == 2   # 用户 + 助手
        assert len(data["reply"]) > 0

    def test_chat_continues_session(self, client):
        """传 session_id,继续会话"""
        # 第一次
        r1 = client.post("/api/chat", json={"message": "你好"})
        session_id = r1.json()["session_id"]

        # 第二次
        r2 = client.post(
            "/api/chat",
            json={"message": "继续", "session_id": session_id},
        )

        assert r2.status_code == 200
        data = r2.json()
        assert data["session_id"] == session_id
        assert data["message_count"] == 4   # 累积

    def test_chat_nonexistent_session(self, client):
        """session_id 不存在"""
        response = client.post(
            "/api/chat",
            json={"message": "你好", "session_id": "nonexistent"},
        )
        assert response.status_code == 404
        data = response.json()
        assert "不存在" in data["message"]

    @pytest.mark.parametrize("bad_payload", [
        {},                                    # 缺 message
        {"message": ""},                       # 空字符串
        {"message": "x" * 20000},              # 超长
        {"message": 123},                      # 类型错
        {"message": "hi", "session_id": 123},  # session_id 类型错
    ])
    def test_chat_validation_errors(self, client, bad_payload):
        """校验错误都返回 422"""
        response = client.post("/api/chat", json=bad_payload)
        assert response.status_code == 422
        data = response.json()
        assert data["code"] == 422
        assert data["message"] == "请求参数校验失败"
        assert isinstance(data["details"], list)

这几个测试覆盖了核心场景:

  • 新建会话
  • 续会话
  • 会话不存在
  • 参数校验(5 种错误场景)

参数化特别适合"边界测试"------一条参数一行,清晰。

3. tests/api/test_sessions.py

python 复制代码
"""会话管理测试"""


@pytest.fixture
def sample_session_data(client):
    """造一个带内容的会话,返回 session_id"""
    r = client.post("/api/chat", json={"message": "测试会话"})
    return r.json()["session_id"]


class TestListSessions:
    def test_empty_list(self, client):
        response = client.get("/api/sessions")
        assert response.status_code == 200
        data = response.json()
        assert data["sessions"] == []
        assert data["total"] == 0

    def test_list_with_sessions(self, client, sample_session_data):
        response = client.get("/api/sessions")
        data = response.json()
        assert data["total"] == 1
        assert data["sessions"][0]["id"] == sample_session_data


class TestGetSession:
    def test_get_existing(self, client, sample_session_data):
        response = client.get(f"/api/sessions/{sample_session_data}")
        assert response.status_code == 200
        data = response.json()
        assert data["id"] == sample_session_data
        assert len(data["messages"]) == 2
        assert data["messages"][0]["role"] == "user"
        assert data["messages"][1]["role"] == "assistant"

    def test_get_nonexistent(self, client):
        response = client.get("/api/sessions/nonexistent")
        assert response.status_code == 404


class TestDeleteSession:
    def test_delete_existing(self, client, sample_session_data):
        response = client.delete(f"/api/sessions/{sample_session_data}")
        assert response.status_code == 204
        # 再查,应该 404
        response = client.get(f"/api/sessions/{sample_session_data}")
        assert response.status_code == 404

    def test_delete_nonexistent(self, client):
        response = client.delete("/api/sessions/nonexistent")
        assert response.status_code == 404


class TestSearch:
    def test_search_finds(self, client, sample_session_data):
        response = client.get("/api/search", params={"q": "测试"})
        assert response.status_code == 200
        data = response.json()
        assert data["count"] >= 1
        assert any(r["id"] == sample_session_data for r in data["results"])

    def test_search_no_result(self, client):
        response = client.get("/api/search", params={"q": "不存在的词汇xyz"})
        assert response.status_code == 200
        assert response.json()["count"] == 0

    def test_search_missing_query(self, client):
        """缺 q 参数"""
        response = client.get("/api/search")
        assert response.status_code == 422

这些测试覆盖了完整的 CRUD + 搜索。

4. tests/api/test_auth.py

认证测试需要"不覆盖"认证依赖------单独写 client fixture:

python 复制代码
"""认证测试"""
import os
import pytest
from fastapi.testclient import TestClient

from ai_assistant.api.app import app
from ai_assistant.api.deps import get_database, get_db_session, get_llm, get_memory


@pytest.fixture
def auth_client(test_db, fake_llm, monkeypatch):
    """需要真实认证的客户端"""
    # 设置 API_KEY
    monkeypatch.setenv("API_KEY", "test-secret-key")

    def override_get_database():
        return test_db

    def override_get_db_session():
        session = test_db.SessionLocal()
        try:
            yield session
            session.commit()
        except Exception:
            session.rollback()
            raise
        finally:
            session.close()

    def override_get_llm():
        return fake_llm

    app.dependency_overrides[get_database] = override_get_database
    app.dependency_overrides[get_db_session] = override_get_db_session
    app.dependency_overrides[get_llm] = override_get_llm

    # 注意:不覆盖 verify_api_key!

    with TestClient(app) as c:
        yield c

    app.dependency_overrides.clear()


class TestApiKey:
    def test_missing_key_returns_401(self, auth_client):
        response = auth_client.get("/api/sessions")
        assert response.status_code == 401
        data = response.json()
        assert "API Key" in data["message"]

    def test_wrong_key_returns_401(self, auth_client):
        response = auth_client.get(
            "/api/sessions",
            headers={"X-API-Key": "wrong-key"},
        )
        assert response.status_code == 401

    def test_correct_key_passes(self, auth_client):
        response = auth_client.get(
            "/api/sessions",
            headers={"X-API-Key": "test-secret-key"},
        )
        assert response.status_code == 200

    def test_health_no_auth_required(self, auth_client):
        """健康检查不需要认证"""
        response = auth_client.get("/health")
        assert response.status_code == 200

    def test_no_env_key_skips_auth(self, test_db, fake_llm, monkeypatch):
        """未配置 API_KEY 时跳过认证"""
        monkeypatch.delenv("API_KEY", raising=False)

        def override_get_database():
            return test_db
        app.dependency_overrides[get_database] = override_get_database

        with TestClient(app) as c:
            response = c.get("/api/sessions")
            assert response.status_code == 200

        app.dependency_overrides.clear()

关键点:auth_client 不覆盖 verify_api_key------让真实的认证逻辑跑起来。

这是"测试认证"的通用模式:

  • 需要测认证 → 不覆盖认证依赖
  • 不需要测认证 → 覆盖它(跳过认证)

monkeypatch.setenv 让环境变量在测试内生效,测完自动恢复(Day 19 学的)。


六、跑测试 + 覆盖率

1. 运行

bash 复制代码
cd day24/ai-assistant-api-v4
pytest tests/api/ -v

输出:

arduino 复制代码
tests/api/test_health.py::TestHealth::test_health_returns_ok PASSED        [  4%]
tests/api/test_health.py::TestHealth::test_health_has_request_id PASSED    [  8%]
tests/api/test_health.py::TestHealth::test_health_has_process_time PASSED  [ 12%]
tests/api/test_chat.py::TestChat::test_chat_creates_new_session PASSED     [ 16%]
tests/api/test_chat.py::TestChat::test_chat_continues_session PASSED       [ 20%]
tests/api/test_chat.py::TestChat::test_chat_nonexistent_session PASSED     [ 24%]
tests/api/test_chat.py::TestChat::test_chat_validation_errors[bad_payload0] PASSED [ 28%]
tests/api/test_chat.py::TestChat::test_chat_validation_errors[bad_payload1] PASSED [ 32%]
...
==================== 23 passed in 2.41s ====================

23 个 API 测试,2.4 秒跑完。

2. 覆盖率

bash 复制代码
pytest --cov=src/ai_assistant --cov-report=term-missing

输出:

erlang 复制代码
Name                                       Stmts   Miss  Cover   Missing
-----------------------------------------------------------------------
src/ai_assistant/api/app.py                   25      0   100%
src/ai_assistant/api/auth.py                  20      2    90%   30-31
src/ai_assistant/api/deps.py                  25      2    92%
src/ai_assistant/api/error_handlers.py        40      5    87%   55-59
src/ai_assistant/api/middleware.py            30      4    87%   42-45
src/ai_assistant/api/routes.py                80      8    90%
src/ai_assistant/db_orm.py                    50      5    90%
src/ai_assistant/storage_orm.py               70      7    90%
...
-----------------------------------------------------------------------
TOTAL                                        450     45    90%

90% 覆盖率,相当不错。

未覆盖的部分大多是"异常分支" ------比如连接池爆了、网络错误等极端场景。这些测试成本高、收益低,可以接受。

3. 加一个 pytest 命令

Makefile:

makefile 复制代码
test-api:
	pytest tests/api/ -v

test-all:
	pytest --cov=src/ai_assistant --cov-report=term-missing

test-fast:
	pytest -m "not integration" -x   # -x: 第一个失败就停

用法:

bash 复制代码
make test-api    # 只跑 API 测试
make test-all    # 全量 + 覆盖率
make test-fast   # 快速模式

七、测试的"陷阱"和"最佳实践"

1. 陷阱:测试污染

症状:单独跑一个测试通过,全量跑失败。

原因: 测试之间共享状态。

常见场景:

python 复制代码
# ❌ 共享 client,没清理
client = TestClient(app)

def test_a():
    client.post("/api/chat", ...)   # 造了数据

def test_b():
    r = client.get("/api/sessions")
    assert r.json()["total"] == 0   # 失败了!因为 test_a 造了数据

修复: 每个测试独立 fixture。

python 复制代码
# ✅ 每个测试独立数据库
@pytest.fixture
def client(tmp_path):
    db = create_db(tmp_path / "test.db")
    ...
    yield client
    # 自动清理

今天的 test_db fixture 用了 tmp_path------每个测试的数据库文件不同,天然隔离。

2. 陷阱:慢

症状: 测试跑了几十秒。

原因:

  • 真的调 LLM:每次几秒
  • 真的建数据库:每次建表
  • 没有 Mock

优化:

python 复制代码
@pytest.fixture(scope="session")
def db_engine():
    """整个测试会话共享一个 engine"""
    ...

scope="session" 让 fixture 只执行一次------但要注意状态污染。

另一种优化:用 SQLite 内存数据库。

python 复制代码
@pytest.fixture
def test_db():
    engine = create_engine(
        "sqlite:///:memory:",
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,
    )
    ...

内存数据库不写磁盘,快 10 倍。 缺点是"内存数据库不能跨连接共享",需要 StaticPool。

今天的 fixture 用文件而不是内存 ------为了更接近真实场景,暴露文件权限等问题。生产测试中往往用内存,加快速度。

3. 最佳实践:只测"契约"

API 测试的重点是"契约",不是"实现"。

✅ 该测的:

  • 状态码(200 / 404 / 422 / 401)
  • 响应结构(字段名、类型)
  • 边界(空输入、超长、类型错)
  • 认证是否生效

❌ 不该测的:

  • 具体的回复内容(模型会变)
  • 内部函数调用(那该用单元测试)
  • 中间件实现细节(改了就误报)

例子:

python 复制代码
# ❌ 测了具体回复
def test_chat(client):
    r = client.post("/api/chat", json={"message": "你好"})
    assert r.json()["reply"] == "你好!我是一个模拟的 AI 助手..."   # 换模型就挂

# ✅ 测了结构
def test_chat(client):
    r = client.post("/api/chat", json={"message": "你好"})
    data = r.json()
    assert r.status_code == 200
    assert "session_id" in data
    assert "reply" in data
    assert isinstance(data["reply"], str)
    assert len(data["reply"]) > 0   # 不空就行

测"契约",不测"内容"------这样改了实现,测试不用跟着改。


八、前端视角:API 测试就是"Mock Server + 断言"

做过前端的你,对 API 测试应该有点感觉:

前端测试 API 的方式:

  1. Mock Server(如 MSW):拦截请求,返回假数据
  2. 对 Mock 断言:验证请求参数、响应结构

FastAPI 测试做的是类似的事:

  • TestClient(app) ≈ MSW 的"拦截器"
  • dependency_overrides ≈ 换掉后端实现
  • assert response.status_code ≈ 前端 expect(res.status).toBe(200)

对照表:

前端(Jest + MSW) FastAPI 测试
setupServer(...handlers) TestClient(app)
mockResolvedValue(data) dependency_overrides[dep] = fake
expect(res.status).toBe(200) assert response.status_code == 200
expect(res.body).toHaveProperty("id") assert "id" in response.json()
expect(fetch).toHaveBeenCalledWith(...) assert mock.called_with(...)
beforeEach / afterEach fixture

核心心智一样: 不测"内部实现",只测"输入 → 输出"。

前端做过的:

js 复制代码
test("登录接口返回 token", async () => {
  const res = await fetch("/api/login", {
    method: "POST",
    body: JSON.stringify({ username: "x", password: "y" }),
  });
  expect(res.status).toBe(200);
  const data = await res.json();
  expect(data).toHaveProperty("access_token");
});

后端现在做的:

python 复制代码
def test_login(client):
    response = client.post("/api/login", json={"username": "x", "password": "y"})
    assert response.status_code == 200
    data = response.json()
    assert "access_token" in data

一模一样。


九、今日踩坑

  1. 忘了 app.dependency_overrides.clear() → 污染后续测试
  2. TestClient 用 with 语句 → 确保 lifespan 执行(数据库初始化)
  3. 认证接口测不了 → 忘了"不覆盖认证依赖",用了默认 client
  4. TestClient 用 async 函数报错 → TestClient 是同步的,要么用同步函数,要么用 AsyncClient
  5. test_db 用了内存数据库但忘了 StaticPool → 连接不通,报错
  6. 断言具体回复内容 → 模型一改就挂,测结构就好
  7. 忘了测边界 → 只测了 happy path,错误码场景没测
  8. 每个测试都重建数据库 → 慢,能共享就 scope="session"
  9. 测试写了 time.sleep → 慢,用 Mock 替换
  10. 测试顺序依赖 → 一定要能独立跑(pytest tests/api/test_chat.py::TestChat::test_chat_creates_new_session)
  11. 忘了测"未认证访问" → 认证的漏洞就在这里
  12. response.json() 前没检查状态码 → 500 返回 HTML,json() 报错,看不出真正问题

十、今日总结

今天核心收获三件事:

  1. FastAPI 测试三件套 :TestClient + dependency_overrides + 临时数据库,一切可测
  2. API 测试测"契约":状态码、响应结构、边界,不测"具体内容"
  3. 认证测试要用"真认证 client":不覆盖认证依赖,才能真正测到

一个感受:

写业务代码时你会觉得"测试好麻烦",但写 API 时你会发现------没有测试更麻烦。

  • 前端说"接口变了" → 你得对一遍 Postman
  • 部署后发现 500 → 你只能翻日志一个个查
  • 重构存储层 → 完全不知道有没有破坏 API

API 测试的价值,就是"改了任何东西,一条命令告诉你有没有搞坏。"

你的 API 现在有 90% 覆盖率保障。 后面 Day 46 换真 LLM、Day 81 加 Agent 工具调用,只要这套测试不挂,就说明没破坏现有功能------这就是"信心"。

Day 24 完成度:✅

明日预告(Day 25)

  • API 性能优化 :
    • 缓存:内存缓存 / Redis 缓存
    • 分页:大数据量分页策略
    • 异步优化:并发处理多请求
    • N+1 查询问题排查
  • 给 API 加一个缓存层
  • 用 ab / hey 压测对比优化前后

最后

如果这篇对你有用,点个赞让我知道有人在看。

评论区互动: 你写过后端测试吗?用的是什么?和前端测试比,你觉得哪个更麻烦?

有问题评论区见,我们第 25 天继续。

100 天,第 25 天见。


附录:系列目录

已完成 ✅

阶段 天数 内容
基础 01-06 Python 语法、数据结构、函数、OOP、文件、依赖管理
网络 07-08 HTTP 请求、异步 asyncio
工程 09-12 正则、装饰器、Pydantic、日志调试
项目 13-14 命令行 AI 助手 v1 / v2
复盘 15 阶段总结
数据 16-17 SQLite 数据库、SQLAlchemy ORM
测试 18-19 pytest 入门 + 进阶
工具 20 CLI 打包与发布
后端 21-24 FastAPI 入门 / 进阶 / 数据库整合 / API 测试 ✅

进行中 🎯

阶段 天数 内容
后端 25-30 性能优化 + 部署上线
大模型 31-45 LLM API + Prompt 工程
RAG 46-60 向量数据库 + 知识库
Agent 61-80 LangChain / 工具调用
实战 81-100 完整 Agent 项目上线

100 天后见,一起从切图仔变成 Agent 开发者。

相关推荐
东莞市云毅网络有限公司1 小时前
监测问句清单去重:停用词表、数字归一与贪心聚类的参数取法
python·数据清洗·rag·企业知识库·文档解析
雪域迷影2 小时前
Python Django 7 小时课程
开发语言·python·django
量化吞吐机2 小时前
最高最低都一样,价格就同样集中吗
人工智能·python
FITA阿泽要努力2 小时前
如何调用 LLM 接口
python·api·agent
江湖中的阿龙2 小时前
大模型应用怎么做评测?从样本集、自动评分到上线回归的完整实战
人工智能·python·回归·大模型评测
雾游2 小时前
用上 uv 后,搭建 Python 项目终于顺手了:从安装到环境恢复,一篇讲清
python
范中勤2 小时前
LangGraph Checkpointer 封装(下):with 的双层上下文与 MemorySaver 为什么不用 with
python·大模型·llm·agent·langgraph
鸿业远图科技2 小时前
丹东市_公交线
python
打工仔折腾 AI3 小时前
在线PDF工具隐私焦虑?把BentoPDF部署到极空间NAS本地处理
人工智能·后端·python·pdf·ai agent 实战