AI API 测试技巧:从单元测试到压测
把大模型 API 接进生产系统之后,大多数人会立刻撞上一堵墙:这东西怎么测?传统 API 的测试套路——固定输入、固定输出、断言相等——在 LLM 面前全部失效。同一个 prompt 问两遍,答案几乎不会一字不差;跑一次测试还要花钱、还要等几秒钟;更麻烦的是,模型一升级,行为就悄悄变了。于是很多团队干脆不测了,把「模型会不会乱说」完全交给运气。
这篇文章给你一套能直接落地的分层测试方案:从毫秒级、零成本的 Mock 单元测试,到花钱但必要的真实集成测试,再到上线前的压测与容量规划。每层解决什么问题、用什么工具、成本多少,一次讲清楚。这也是我们对内部接入 40+ 模型的AI API 网关做质量保障时实际在用的方法。
一、为什么 AI API 的测试和传统 API 不一样
核心差异有三个。第一是非确定性:同样的输入,输出在合法范围内浮动,断言不能等于「字符串完全一致」,而必须是「结构正确 + 语义达标」。第二是成本与延迟:一次真实调用要花钱、要等秒级响应,测试不可能像普通单元测试那样跑几千次。第三是退化风险:模型版本、提示词、参数任何一处变动,都可能让输出质量整体下滑,而且往往是悄悄下滑。
应对方法不是「不测」,而是分层:上层用 Mock 保证逻辑正确且快,下层用真实调用保证行为正确且可信。每一层只负责自己的问题,成本可控,速度可控。
二、第一层:Mock 单元测试——毫秒级、零成本
凡是「我的代码怎么处理模型返回」的逻辑,都应该用 Mock 测。解析、分支、重试、超时、错误处理——这些逻辑与模型无关,完全可以确定性地测。用 responses 或 httpx.MockTransport 拦截 HTTP 请求,返回固定的假响应:
# test_chat.py
import pytest, responses
@responses.activate
def test_parse_reply():
responses.add(
responses.POST, "https://api.dr-ai.top/v1/chat/completions",
json={"choices": [{"message": {"content": "{\"city\": \"北京\"}"}}]},
)
result = chat_and_parse("北京今天天气怎么样?")
assert result["city"] == "北京"
@responses.activate
def test_retry_on_429():
responses.add(responses.POST, URL, status=429, json={"error": {"message": "rate limited"}})
responses.add(responses.POST, URL, status=200, json=ok_body())
# 断言第一次失败后自动退避重试,最终成功
assert chat("hi") == "ok"
assert responses.calls[0].request.url == URL
这一层要覆盖的典型场景:响应解析成功、JSON 损坏、空内容、HTTP 429/5xx、超时、网络错误。把这些写全,你的「AI 应用」里 80% 的代码就和「AI」无关了,可以放心快速迭代。Mock 是结构化输出解析测试的主力,强烈建议配套使用。
三、第二层:契约测试与 Schema 校验
Mock 测的是「我的代码」,契约测的是「模型答应给我什么」。约定好输出格式之后,把它写成 JSON Schema,每次真实调用都校验一遍:
import jsonschema
SCHEMA = {
"type": "object",
"properties": {
"city": {"type": "string"},
"temperature": {"type": "number"},
"units": {"enum": ["celsius", "fahrenheit"]},
},
"required": ["city", "temperature", "units"],
"additionalProperties": False,
}
def validate(output: str) -> dict:
data = json.loads(output) # 1. 可解析
jsonschema.validate(data, SCHEMA) # 2. 结构正确
return data
把 Schema 校验挂在所有真实调用的出口上,任何一次「模型不守规矩」都会立刻暴露。更高级的做法是「契约测试」:把固定输入 + 期望结构固化成用例集,模型升级或换模型时批量跑一遍,谁破坏了契约一目了然。这比肉眼抽查可靠得多,也是模型评估的简化版——想系统化地评估模型质量,可以看我们的结构化输出指南和英文站的模型评估方法论。
四、第三层:集成测试——花小钱买真话
Mock 再全,也测不出「模型真实返回长什么样」。集成测试用真实 API 跑小样本,验证端到端行为。要点是控制成本:
- 样本要小:每个用例 1~3 次调用,全量集成测试控制在几十次请求以内,用 mini 档模型跑能进一步省钱(GPT-5-mini 还是 GPT-5?讲了怎么选)。
- 用测试账号和独立 Key:限流、配额都打在测试 Key 上,别污染生产数据。
- 断言「结构 + 语义」,不断言逐字:结构用 Schema,语义用关键词/相似度/人工抽检。
- 固定模型版本:测试里显式指定模型和参数(temperature=0 之类),避免浮动。
集成测试最好挂在 CI 的定时任务或发布流水线里,每天跑一次。花几块钱,换来「今天模型没抽风」的安心,非常划算。
五、第四层:回归测试与黄金样本集
模型和提示词是会「漂移」的。建立一份黄金样本集(golden set):50~200 条覆盖典型场景的输入,配上「什么算通过」的标准——可以是结构化字段全对、可以是答案包含关键信息、也可以是人工打过分数的参考答案。每次改提示词、换模型、升级 SDK 之后,全量跑一遍:
| 变化 | 必须跑的测试 | 重点观察 |
|---|---|---|
| 改提示词 | 黄金样本全量 | 通过率、输出格式 |
| 换模型 / 升级版本 | 黄金样本 + 契约测试 | 成本、延迟、质量分 |
| 改解析代码 | 单元测试 + 契约测试 | 解析成功率 |
| SDK / 网关升级 | 全量回归 | 兼容性、错误码 |
黄金样本集是 AI 应用最值得投入的资产之一——它把「模型变笨了」这种模糊的抱怨,变成了「通过率从 98% 掉到 91%」这样可行动的信号。
六、第五层:压测与容量规划
上线前还要回答一个问题:扛得住多少并发?AI API 的压测和普通接口不同——单请求耗时秒级、成本随请求数线性增长、上游还有限流。用 Locust 模拟真实负载,重点看三个指标:
# locustfile.py
from locust import HttpUser, task, between
class ChatUser(HttpUser):
wait_time = between(0.5, 2.0)
@task
def chat(self):
self.client.post("/v1/chat/completions", json={
"model": "gpt-5-mini",
"messages": [{"role": "user", "content": "用一句话介绍量子计算"}],
"max_tokens": 200,
"stream": True, # 流式更接近真实场景
})
压测时要同时盯:TTFT 与 TPOT(感知延迟)、P95/P99 延迟、错误率与限流触发次数。得到容量上限后反推:峰值流量 × 安全系数 = 需要预留的配额。特别提醒:上游模型商的限流(RPM/TPM)往往是瓶颈,网关侧要做好排队和退避,否则压测会变成「雪崩演练」。延迟优化和限流的细节,我们分别在《LLM API 延迟优化》和API 接入安全里展开过。
七、成本与测试预算管理
测试要花钱,但可以控制:给每类测试设月度预算上限(比如集成测试每月 $50),超过自动告警;能用 mini 模型跑的就别用旗舰;Mock 层能覆盖的绝不上真实调用。测试成本本质上是保险金——一个月几十美元,换的是「上线不被模型坑」。
八、常见坑与建议
- 在 Mock 里复制真实响应:从线上日志里截取真实响应作为 Mock 数据,别手写理想化的假 JSON。
- 重试放大:测试里故意注入 429/5xx,验证退避逻辑,否则故障时你的重试会把上游打爆。
- 超时设置不合理:超时 = P95 延迟 × 2,别用 3 秒这种拍脑袋值。
- 用生产 Key 跑测试:一定要隔离,否则测试流量会污染你的用量统计和告警。
- 不测流式:如果生产用 stream=true,测试就必须测流式路径——SSE 的解析和错误处理是完全不同的代码。
测试不是 AI 应用的奢侈品,是及格线。把这五层搭起来,模型升级、提示词迭代、流量增长就都不再是「开盲盒」。想快速搭好测试环境?注册 DrAI,一个 Key 就能在测试环境里切换 40+ 模型,黄金样本集的模型对比跑起来很方便。