尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
大模型 API 单元测试怎么 mock 不误伤
跑大模型 API 的单元测试时测试还没发出请求就在读取密钥环境变量的那一行抛出了KeyError如果把密钥临时写进代码用例又会真的访问 HTTPS慢而且不稳定。根因通常不是测试框架而是被测代码把客户端创建与网络调用绑得太紧。读完这篇你能拿到一套可复现的 mock 方案使用真实接入参数创建客户端同时把网络调用锁在测试桩里不让用例产生真实请求。1 场景与前置条件这篇教程解决的不是“怎么调用大模型接口”而是“调用大模型 API 的代码怎么做单元测试”。不少项目已经把 SDK 封装成complete(prompt)一类函数测试时却绕不开真实网络。你手上应该已经有 Python 3.10、OpenAI SDK 1.x、pytest、一个sk_live_开头的密钥以及从模型广场复制来的调用名例如glm-4.6。环境限制要说清。本文示例只覆盖 OpenAI 兼容的/chat/completionsBase URL 使用该平台提供的固定地址。如果你走 Anthropic 兼容端点/messages请求体必须传max_tokens测试桩目标也要从chat.completions.create改成messages.create。思路一致但字段不同别直接套用。2 环境准备先装依赖pipinstallopenai pytest python-dotenvpython-dotenv可选不是运行硬条件。密钥不要写进代码放到.env并让.gitignore包含这一项。测试前导出环境变量exportSILVAMUX_API_KEYsk_live_xxxx为什么不用硬编码密钥是创建时只显示一次的凭证提交进仓库等于把凭证交给所有看得到代码的人。CI 环境可以注入同名变量测试代码不需要变化。Base URL 也不要从配置文件里随手编OpenAI SDK 会在这个地址后面自动拼接/chat/completions所以必须写完整的/api/v1这段。3 最小可跑示例先创建llm_client.pyfromopenaiimportOpenAIimportos clientOpenAI(api_keyos.environ[SILVAMUX_API_KEY],base_urlhttps://www.silvamux.com/api/v1,)defcomplete(prompt:str,model:strglm-4.6)-str:respclient.chat.completions.create(modelmodel,messages[{role:user,content:prompt}],)returnresp.choices[0].message.content这段代码的关键是api_key和base_url。它们决定客户端能不能走到兼容端点。model必须填模型广场详情页里的稳定调用名不要根据展示名自行拼成glm-4.6-chat那样会返回gateway_error。messages是列表哪怕只传一句 prompt也要写成[{role: user, content: prompt}]直接传字符串会触发参数校验错误。接着写测试test_llm_client.pyfromunittest.mockimportpatch,MagicMockimportllm_clientdeftest_complete_mocks_http():fake_respMagicMock()fake_resp.choices[0].message.contentmock replywithpatch.object(llm_client.client.chat.completions,create,return_valuefake_resp):resultllm_client.complete(hello)assertresultmock reply这个测试不会有任何网络请求。patch.object替换的是已经创建好的client.chat.completions.create不是openai.OpenAI类。如果你 patch 类方法模块导入时已经存在的实例不会被影响这是单测 mock 常见的失败点。要盯住 patch 目标。4 逐步扩展第一处扩展把client作为参数注入别再让函数依赖模块级全局对象。defcomplete(client,prompt:str,model:strglm-4.6)-str:respclient.chat.completions.create(modelmodel,messages[{role:user,content:prompt}],)returnresp.choices[0].message.content改完后测试直接传一个 stub不需要关心 client 在哪创建。修改前测试必须知道llm_client.client这个内部属性修改后函数签名更清楚也更容易替换成异步客户端或假对象。这个改动没有改变调用协议。第二处扩展处理流式返回里的空choiceschunk。很多大模型 API 的流式接口在结束前会返回一个只有usage字段、没有choices的 chunk代码如果直接访问chunk.choices[0]最后会抛IndexError。写一个能跳过该 chunk 的版本defstream_complete(client,prompt:str,model:strglm-4.6):withclient.chat.completions.create(modelmodel,messages[{role:user,content:prompt}],streamTrue,)asstream:forchunkinstream:ifchunk.choicesandchunk.choices[0].delta.content:yieldchunk.choices[0].delta.content测试时用两个假 chunk 模拟第一个正常带delta.content第二个choices[]表示用量通知。断言结果只包含第一段内容。if chunk.choices这一行不能省省略后错误只在长响应里出现平时短响应可能测不出来。5 排错KeyError: ‘SILVAMUX_API_KEY’直接原因导入模块时os.environ[...]没读到环境变量。修法先按环境准备里的方式导出或在测试启动前用 monkeypatch 给os.environ注入同名键。不要临时把密钥写进源码那样会让 mock 失去意义。openai.APIConnectionError: Connection error.直接原因Base URL 填得不完整SDK 连接不到目标路径。修法回到llm_client.py检查 Base URL补齐www和/api/v1两段。不要只查 API key这个报错先于认证发生。{“error”:{“message”:“model not found”,“type”:“gateway_error”,“code”:“MODEL_NOT_FOUND”}}直接原因model参数不在模型调用名列表里常见于把展示名自动拼成带版本后缀的字符串。修法打开模型广场详情页复制稳定 alias不要自己造。mock 后仍发出真实请求直接原因patch 目标写成了openai.OpenAI但模块里已经创建好客户端实例。修法patchllm_client.client.chat.completions.create或者采用上面扩展一里的依赖注入让测试不依赖全局对象。常见问题Q: 能不能完全不设置那个密钥环境变量跑单元测试A: 可以。把读取环境变量的代码改成os.getenv并提供默认值测试前把create方法 mock 掉即可。客户端初始化阶段通常不会校验密钥只有真正发出请求时才可能返回 401所以纯本地单测不依赖真实凭证。Q: 为什么流式测试里会多一个空的 choices chunkA: 这是 OpenAI 兼容端点在流末尾返回 usage 的行为choices可能是空列表。代码必须用if chunk.choices跳过不能只依赖data: [DONE]结束符判断因为 SDK 已经把协议解析成 chunk 对象循环里会更早遇到这个空块。Q: 想 mock Anthropic 兼容端点的请求应该改哪里A: 把调用从client.chat.completions.create改成client.messages.create请求体里必须传max_tokens字段形状也不同。测试桩的目标也跟着换断言参数时注意content是列表而不是字符串不能照搬/chat/completions的断言。以上示例在千木SilvaMux的 OpenAI 兼容端点上验证模型调用名为 glm-4.6。流式判断参考该端点的 usage chunk 行为。具体模型调用名和错误处理参数以模型广场与错误页为准。
RELATED

相关推荐

初识编程

初识编程

你好!欢迎来到我的博客。 我是一个对技术充满好奇心的初学者。在日常生活中,我可能是一个普通的学生或职场人,但在内心深处,我一直对"计算机如何运转""那些神奇的应用是如何被创造出来的"这些问题充满兴趣。今…

📅 2026/10/15 19:19:58
同样是扫码模块,为什么门禁能用收银台却不行?场景适配全解析

同样是扫码模块,为什么门禁能用收银台却不行?场景适配全解析

一个真实的采购场景:某集成商做园区门禁项目,用了一款在收银台上表现很好的扫码模块,结果用户投诉不断——站在闸机前扫手机码,十次有三次读不出,尤其下午阳光斜射的时候几乎全废。问题不在模块质量,而在场…

📅 2026/10/15 19:19:58
数据仓库宽表模型设计:核心思路、实操与性能优化

数据仓库宽表模型设计:核心思路、实操与性能优化

1. 宽表模型到底在解决什么问题第一次接触数据仓库宽表模型的人,脑子里冒出来的第一个疑问往往是:明明数据库已经三范式设计得好好的,为什么还要再搞一层宽表出来?这不是冗余吗?我刚开始做数仓的时候也有这个困惑&…

📅 2026/10/15 19:19:58
MORE NEWS

更多资讯

📰

51单片机光照强度显示:光敏电阻分压、ADC采样与标定的完整实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

从Apollo-11机载计算机看极端约束下的软件工程智慧

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Qt多屏自适应方案:解决DPI缩放导致的字体模糊与布局错乱

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

51单片机光照强度显示:光敏电阻与BH1750方案对比与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

茶叶萃取程序:用代码建模水温时间与茶多酚析出量

先声明一下:这篇文章不是“科普茶学理论”,而是我从一个写程序、又真的每天泡茶喝的人的角度,去梳理一遍“茶叶萃取程序”这件事。标题里那几个关键词——水温、时间、茶多酚析出量、口感、健康浓度——看着像是实验室课题,实际上…

📰

计算机组成原理课设:从指令集到数据通路的CPU设计实战指南

简介:面向山东大学20级计算机专业学生的《计算机组成原理》课程设计资料,围绕电路图设计与命令集定义展开,内容涵盖数据表示、运算器、控制器、存储系统等核心知识点,并涉及原理图绘制、指令集设计、实验报告撰写等完整环节&#…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬