Codebuff SDK 端到端测试实战指南:E2E、集成测试与可运行示例全解析
【免费下载链接】freebuffThe free coding agent项目地址: https://gitcode.com/GitHub_Trending/cod/freebuff
Codebuff SDK(@codebuff/sdk)为开发者提供了调用 Codebuff AI 编码 Agent 的完整运行时能力。本文围绕仓库中sdk/e2e目录的测试体系展开,系统讲解其四层测试结构(端到端测试、集成测试、单元测试、可运行示例)、环境变量与运行方式、测试工具设施(EventCollector、Mock 机制、测试夹具)以及标准的测试编写模式。读完本文,你将能够直接运行这套测试套件、理解流式事件与子 Agent 流的验证方法,并掌握编写属于自己的 SDK E2E 测试的完整套路。
一、sdk/e2e目录结构与测试分层
sdk/e2e目录是 SDK 的测试与示例集散地,核心文档为 sdk/e2e/README.md。整个目录按职责划分为七个子目录:
sdk/e2e/ ├── streaming/ # E2E 测试:流式行为(子 Agent 流、并发流) ├── workflows/ # E2E 测试:多步骤工作流(多轮对话、错误恢复) ├── custom-agents/ # E2E 测试:自定义 Agent 与自定义工具集成 ├── features/ # E2E 测试:SDK 特性覆盖(projectFiles、knowledgeFiles、maxAgentSteps) ├── integration/ # 集成测试(SDK + API 机制) ├── examples/ # 可运行示例脚本(不是测试) └── utils/ # 共享工具 ├── __tests__/ # 工具类的单元测试 ├── event-collector.ts ├── get-api-key.ts └── test-fixtures.ts这种分层设计遵循了经典测试金字塔思路:E2E 测试验证完整用户工作流(真实或模拟 API 调用),集成测试验证 SDK 与后端 API 之间的机制契约(事件类型、事件顺序、流式分块、连接检测),单元测试则纯本地、无外部依赖地验证工具类本身。
测试脚本定义在 sdk/package.json 中,四个核心命令分别为:
| 命令 | 实际执行内容 |
|---|---|
test:e2e | bun test e2e/streaming/ e2e/workflows/ e2e/custom-agents/ e2e/features/ |
test:integration | bun test e2e/integration/ |
test:unit:e2e | 通过test:files汇总src与e2e/utils下的*.test.ts(排除*.integration.test.ts) |
test | bun test $(bun run --silent test:files) |
从脚本可以看出,E2E 与集成测试全部基于 Bun 的测试运行器(bun:test),这也是所有测试文件统一使用import { describe, test, expect, beforeAll } from 'bun:test'的原因。
二、前置条件:环境变量与 Mock 模式
根据 sdk/e2e/README.md 与 sdk/e2e/utils/get-api-key.ts,运行测试需要以下前置条件:
- API Key:E2E 与集成测试需要设置
CODEBUFF_API_KEY环境变量; - Opt-in 开关:本地进行真实 API 调用需设置
RUN_CODEBUFF_E2E=true(CI 环境默认自动执行); - 优雅跳过:文档说明未设置 API Key 时测试会优雅跳过。
深入源码会发现一套更有意思的实现机制:getApiKey()并不是简单地抛错或跳过,而是区分两种模式:
// sdk/e2e/utils/get-api-key.ts(要点) const shouldRunLiveE2e = process.env.RUN_CODEBUFF_E2E === 'true' export function getApiKey(): string { if (shouldRunLiveE2e) { const apiKey = process.env.CODEBUFF_API_KEY if (!apiKey) { throw new Error('CODEBUFF_API_KEY environment variable is required for live e2e tests. ...') } return apiKey } setupE2eMocks() // 注入全部 Mock process.env.CODEBUFF_API_KEY = E2E_MOCK_API_KEY return E2E_MOCK_API_KEY // 'codebuff-e2e-mock' }也就是说,未设置RUN_CODEBUFF_E2E=true时,测试并不会真的打到线上,而是自动进入 Mock 模式:注入一个固定的模拟 Key(codebuff-e2e-mock),保证测试在本地可确定性运行。skipIfNoApiKey()的实现恒返回false,因此所有测试用例始终执行,只是底层从"真实调用"切换为"模拟调用"。
Mock 机制底层实现
Mock 的核心实现在 sdk/e2e/utils/e2e-mocks.ts,它使用 Bun 测试运行器内置的spyOn对 SDK 的数据库层与 LLM 层进行替换:
- 数据库模块(
sdk/src/impl/database):替换getUserInfoFromApiKey、fetchAgentFromDatabase、startAgentRun、finishAgentRun、addAgentStep,其中 Agent 模板由buildMockAgentTemplate按publisherId/agentId@version的 ID 格式动态构造; - LLM 模块(
sdk/src/impl/llm):替换promptAiSdkStream、promptAiSdk、promptAiSdkStructured,模拟流式输出与结构化输出; - 连接检测:将
CodebuffClient.prototype.checkConnection直接 Mock 为恒返回true。
模拟 LLM 层还内置了一套"关键词响应路由"(buildMockResponseText):例如提示词包含weather会返回The weather is sunny, temperature 72F.,包含favorite number is会返回Got it.,包含2 + 2会返回4。工具调用则由buildMockToolCall按提示词关键词自动选择get_weather、execute_sql、apply_patch、fetch_api等工具并生成输入。这正是多轮对话、天气 Agent 等 E2E 测试能够在本地稳定断言的根基。
错误识别辅助
sdk/e2e/utils/get-api-key.ts 还提供两个断言辅助函数:
isAuthError(output):当输出类型为error且消息包含authentication、api key或unauthorized时判定为鉴权错误;isNetworkError(output):当状态码为 408(超时)、429(限流)或 5xx,或消息包含network error时判定为网络错误。
二者共同帮助测试区分"鉴权失败"与"网络抖动",在 CI 中便于对偶发错误做降级处理。
三、E2E 测试详解(test:e2e)
E2E 测试通过bun run test:e2e运行,覆盖四类场景,对应四个子目录:
| 目录 | 覆盖场景 | 对应测试文件 |
|---|---|---|
streaming/ | 子 Agent 流式输出、并发流隔离 | subagent-streaming.e2e.test.ts、concurrent-streams.e2e.test.ts |
workflows/ | 多轮对话、错误恢复 | multi-turn-conversation.e2e.test.ts、error-recovery.e2e.test.ts |
custom-agents/ | 自定义 Agent + 自定义工具 | weather-agent.e2e.test.ts、database-query-agent.e2e.test.ts、api-integration-agent.e2e.test.ts、apply-patch-tool.e2e.test.ts |
features/ | projectFiles、knowledgeFiles、maxAgentSteps | project-files.e2e.test.ts、knowledge-files.e2e.test.ts、max-agent-steps.e2e.test.ts |
3.1 流式行为:子 Agent 流与并发流
子 Agent 流测试(subagent-streaming.e2e.test.ts)验证嵌套子 Agent 的事件流与父子关系,核心断言包括:
subagent_start与subagent_finish事件按agentId成对出现;subagent_start事件的agentId、agentType、displayName均为字符串,onlyChild为布尔值,parentAgentId与prompt为可选的同类型字段;- 子 Agent 的文本块通过
handleStreamChunk以subagent_chunk结构转发,包含agentId、agentType、chunk字段; - 同一
agentId不应出现重复的subagent_start事件。
测试中使用的 Agent 是codebuff/base@latest,其特点是会在执行中派生文件选择、代码搜索等子 Agent,从而天然触发上述事件。
并发流测试(concurrent-streams.e2e.test.ts)则验证多个并发client.run()之间的事件流互不干扰:为每个 run 单独创建EventCollector,通过Promise.all同时发起 2~3 个 run,断言每个收集器都有独立的start/finish事件、独立的streamChunks,且错误列表为空。该测试在工程上很有价值——它证明了 SDK 客户端在多 run 场景下的流隔离能力。
3.2 多轮对话:previousRun链式续聊
多轮对话测试(multi-turn-conversation.e2e.test.ts)验证previousRun机制:第一次 run 返回的RunState作为下一次run()的previousRun传入,从而保持上下文。
测试用三段式对话验证记忆保持:先让 Agent 记住"最喜欢的数字是 42",第二轮问"我告诉你的最喜欢的数字是什么",断言collector2.getFullText()中包含42;随后用 todo app 场景做三连击,第三轮断言回复中包含todo或task。从源码看,previousRun正是 sdk/src/run.ts 中RunOptions的字段,其注释明确指出:传入上一次run()返回的 JSON 状态即可延续会话上下文。
3.3 错误恢复与中止
错误恢复测试(error-recovery.e2e.test.ts)覆盖四种异常场景:
- 空 prompt:不应崩溃,至少产生
start事件; - 不存在的 Agent:
nonexistent-agent-that-does-not-exist-12345,输出要么是带message的error,要么被优雅处理; - 特殊字符 prompt:emoji、引号、反引号、换行混排的 prompt 应正常完成并产生
finish; - AbortController 中止:
signal: abortController.signal在 500ms 后中止长任务,断言结果为 error 或已有部分事件。
其中signal参数在 sdk/src/run.ts 的RunOptions中有明确定义,说明 SDK 原生支持AbortSignal取消机制。
3.4 自定义 Agent 与自定义工具
自定义 Agent 测试演示了 SDK 最强大的扩展能力:通过agentDefinitions与customToolDefinitions注入自定义 Agent 和工具。以天气 Agent(weather-agent.e2e.test.ts)为例:
const weatherAgent: AgentDefinition = { id: 'weather-agent', model: 'anthropic/claude-sonnet-4.5', displayName: 'Weather Agent', toolNames: ['get_weather'], instructionsPrompt: `You are a helpful weather assistant. When asked about weather, use the get_weather tool to fetch current conditions.`, } const weatherTool = getCustomToolDefinition({ toolName: 'get_weather', description: 'Get current weather for a city', inputSchema: z.object({ city: z.string().describe('Name of the city') }), exampleInputs: [{ city: 'New York' }], execute: async ({ city }) => { const weather = MOCK_WEATHER_DATA[city] || { temp: 65, condition: 'Unknown' } return [{ type: 'json', value: { city, temperature: weather.temp, condition: weather.condition } }] }, })随后通过client.run({ agent: 'weather-agent', agentDefinitions: [weatherAgent], customToolDefinitions: [weatherTool], ... })执行,断言产生了tool_call事件(toolName === 'get_weather'),且回复包含天气相关信息。
这里的getCustomToolDefinition实现在 sdk/src/custom-tool.ts,其参数含义如下:
| 参数 | 类型 | 说明 |
|---|---|---|
toolName | string | 工具名称;若与内置工具同名会得到类型层面的报错提示,要求改用overrideTools |
inputSchema | z.ZodType | Zod 4 输入校验 schema,LLM 调用工具时按此校验参数 |
description | string | 传给 LLM 的工具描述,说明工具做什么、何时使用 |
endsAgentStep | boolean | 默认true,表示该工具调用会作为一步的终止序列,LLM 必须等待工具结果后才能继续调用其他工具 |
exampleInputs | Input[] | 示例输入,用于帮助 LLM 理解调用格式 |
execute | (params) => Promise<ToolResultOutput[]> | 工具执行体,返回ToolResultOutput数组(如{ type: 'json', value }) |
配套的数据库查询 Agent(database-query-agent.e2e.test.ts)展示了execute_sql工具与MOCK_DATABASE夹具(users 表含 Alice/Bob/Charlie)的组合用法,并实现了简单的 WHERE 子句解析;API 集成 Agent(api-integration-agent.e2e.test.ts)展示了fetch_api工具,对jsonplaceholder/example域名返回 Mock 数据、对其他 URL 走真实 fetch(带 5 秒 AbortController 超时);apply_patch 测试(apply-patch-tool.e2e.test.ts)则在临时目录中真实执行文件补丁并回读验证文件内容。
3.5 SDK 特性覆盖
- projectFiles(project-files.e2e.test.ts):将项目文件以
{ 'src/index.ts': '...' }形式注入,Agent 可据此列出文件、分析内容。夹具定义在 sdk/e2e/utils/test-fixtures.ts 的SAMPLE_PROJECT_FILES中; - knowledgeFiles(knowledge-files.e2e.test.ts):注入"知识文件",Agent 能从中读取秘密暗号(
PINEAPPLE42)或公司价值观(Innovation / Integrity)。与projectFiles的区别在 sdk/src/client.ts 的注释中有明确说明:knowledgeFiles会直接加入 Agent 上下文,而projectFiles用于帮助 Codebuff 挑选合适的源码文件作为上下文; - maxAgentSteps(max-agent-steps.e2e.test.ts):限制 Agent 最大执行步数(如 5、2),作为防止 Agent 失控的安全阀。sdk/src/client.ts 的文档注释建议一个合理的默认值约为 20。
四、集成测试详解(test:integration)
集成测试通过bun run test:integration运行,聚焦 SDK 与 API 的机制层契约,目录为sdk/e2e/integration/:
| 测试文件 | 验证内容 |
|---|---|
| event-types.integration.test.ts | 验证所有PrintModeEvent类型均被正确发射 |
| event-ordering.integration.test.ts | 验证事件顺序:start → content → finish |
| stream-chunks.integration.test.ts | 验证handleStreamChunk回调 |
| connection-check.integration.test.ts | 验证checkConnection()方法 |
4.1 事件类型契约
事件类型测试断言了PrintModeEvent的核心结构(类型定义见 common/src/types/print-mode.ts):
start:run 开始时发射,携带messageHistoryLength(number);finish:run 结束时发射,携带totalCost(number,非负);text:响应生成过程中的文本事件,多个text事件拼接即为完整回复;tool_call/tool_result:工具调用时成对出现,分别携带toolCallId、toolName、input与toolCallId、toolName、output;- 此外还有
error、subagent_start、subagent_finish、reasoning_delta、download等类型。
完整的PrintModeEvent判别联合(discriminated union)由 Zod 的printModeEventSchema定义,SDK 在 sdk/src/index.ts 中将其作为公共类型导出。
4.2 事件顺序契约
事件顺序测试(event-ordering.integration.test.ts)是"流式渲染正确性"的基石,验证了五条规则:
start事件必须是第一个事件(findIndex结果为 0);finish事件必须晚于所有text事件;- 同一
toolCallId的tool_result必须晚于其tool_call; - 标准流程
start → text → finish可用collector.verifyEventOrder(['start', 'finish'])验证; - 最后一个
finish之后不允许再出现任何非 finish 事件。
这些断言直接支撑了 UI 层"先渲染开始状态、流式追加文本、最终收尾"的渲染逻辑。
4.3 流式分块契约
流式分块测试(stream-chunks.integration.test.ts)验证handleStreamChunk的三种块类型:
- 字符串块:文本流式输出,多个块拼接成完整文本;
subagent_chunk:子 Agent 的文本块,含agentId、agentType、chunk;reasoning_chunk:推理过程块,含agentId、ancestorRunIds、chunk(类型定义同样在 sdk/src/run.ts 的CodebuffClientOptions中)。
测试还验证了块是增量到达的(记录每个块到达时间戳,长响应应有多个块),以及空 prompt、超长响应、特殊字符(emoji、引号、换行、制表符)场景下的流式健壮性。
4.4 连接检测
连接检测测试(connection-check.integration.test.ts)验证checkConnection()在后台可达时返回true且返回值为布尔类型。其实现位于 sdk/src/client.ts,通过请求/api/healthz并校验status === 'ok'实现,带 5 秒超时;BYOK 模式下则改为探测 OpenRouter 的key或models端点。
五、单元测试:EventCollector(test:unit:e2e)
单元测试目录为sdk/e2e/utils/__tests__/,目前包含 event-collector.test.ts,纯本地运行、无任何外部依赖。测试覆盖EventCollector的全部 API:
handleEvent:按序收集事件,并单独追踪error事件;handleStreamChunk:收集字符串块与subagent_chunk;getEventsByType/hasEventType/getFirstEvent/getLastEvent:按类型查询、判断、取首取尾;getFullText/getFullStreamText:拼接全部文本事件 / 拼接字符串流块;getSubagentChunks(agentId):按 Agent 过滤子 Agent 流块;verifyEventOrder:按期望顺序校验事件出现次序(支持部分顺序);getUniqueEventTypes/countEvents/clear/getSummary:去重、计数、清空与调试摘要。
EventCollector本身实现在 sdk/e2e/utils/event-collector.ts,是整个测试套件的"断言中枢"——几乎所有 E2E 与集成测试都通过它收集事件再断言。其StreamChunk类型(string | subagent_chunk | reasoning_chunk)与 sdk/src/run.ts 中的回调签名完全对齐。
六、可运行示例:开箱即用的 SDK 用法
examples/目录包含 6 个可运行脚本(非测试),覆盖了 SDK 最常见的六类工程场景。运行方式为:
bun run sdk/e2e/examples/code-reviewer.example.ts| 示例 | 功能描述 | 关键点 |
|---|---|---|
| code-reviewer.example.ts | AI 代码审查 | 提交含除零 Bug 的divide函数,让 Agent 找出问题 |
| code-explainer.example.ts | 用通俗语言解释代码 | 针对fetchUserData异步函数 |
| commit-message-generator.example.ts | 根据 diff 生成提交信息 | 传入 git diff 文本 |
| sdk-lint.example.ts | AI 驱动的 Linter | 让 Agent 以 linter 身份给出具体反馈 |
| sdk-refactor.example.ts | 代码重构 | 要求使用现代 JavaScript 特性重写 |
| sdk-test-gen.example.ts | 单元测试生成 | 要求生成 Jest 测试 |
所有示例的结构高度一致,是最佳实践模板:
- 读取
CODEBUFF_API_KEY环境变量,缺失即报错退出; - 构造
new CodebuffClient({ apiKey }); - 调用
client.run({ agent: 'codebuff/base2@latest', prompt, handleStreamChunk }); - 通过
handleStreamChunk将字符串块实时写入process.stdout实现流式打印; - 检查
result.output.type === 'error'处理失败。
其中handleStreamChunk只对typeof chunk === 'string'的块做输出、忽略subagent_chunk等结构化块,是流式 UI 的极简范例。
七、标准测试编写模式
sdk/e2e/README.md 给出了两类官方推荐的测试模板,可直接复用。
7.1 E2E 测试模板
import { describe, test, expect, beforeAll } from 'bun:test' import { CodebuffClient } from '../../src/client' import { EventCollector, getApiKey, skipIfNoApiKey, isAuthError, DEFAULT_AGENT, DEFAULT_TIMEOUT } from '../utils' describe('E2E: My Test', () => { let client: CodebuffClient beforeAll(() => { if (skipIfNoApiKey()) return client = new CodebuffClient({ apiKey: getApiKey() }) }) test('does something', async () => { if (skipIfNoApiKey()) return const collector = new EventCollector() const result = await client.run({ agent: DEFAULT_AGENT, prompt: 'Test prompt', handleEvent: collector.handleEvent, }) if (isAuthError(result.output)) return expect(result.output.type).not.toBe('error') }, DEFAULT_TIMEOUT) })模板要素拆解:
beforeAll+skipIfNoApiKey():集中初始化客户端,保持用例简洁;getApiKey():自动处理真实 Key 与 Mock Key 的切换;EventCollector:统一收集事件用于断言;isAuthError(result.output):鉴权错误时直接返回,避免 CI 误报;DEFAULT_TIMEOUT:超时上限为 120 秒(2 分钟,定义于 sdk/e2e/utils/test-fixtures.ts),复杂场景如子 Agent 流会翻倍使用(DEFAULT_TIMEOUT * 2);DEFAULT_AGENT:默认 Agent 为base2。
7.2 单元测试模板
import { describe, test, expect, beforeEach } from 'bun:test' import { EventCollector } from '../event-collector' describe('Unit: EventCollector', () => { let collector: EventCollector beforeEach(() => { collector = new EventCollector() }) test('collects events', () => { collector.handleEvent({ type: 'start', messageHistoryLength: 0 }) expect(collector.events).toHaveLength(1) }) })单元测试无需 API Key、无网络请求,beforeEach中重建被测对象以保证用例隔离。
八、测试数据夹具一览
sdk/e2e/utils/test-fixtures.ts 提供了全套可复用测试数据:
SAMPLE_CODE:六段示例代码(简单函数、含 Bug 的除法、含 Bug 的类、异步函数、React 组件等);SAMPLE_DIFFS:单文件与多文件的 git diff 样本(供 commit message、代码审查类测试使用);SAMPLE_PROJECT_FILES:模拟项目文件树(src/index.ts、src/calculator.ts、package.json、README.md),用于projectFiles注入测试;MOCK_WEATHER_DATA/MOCK_DATABASE:天气城市数据与用户表数据,供自定义工具测试使用;TEST_PROMPTS:按场景命名的常用提示词集合;DEFAULT_AGENT/DEFAULT_TIMEOUT:默认 Agent(base2)与默认超时(120 秒)。
九、从测试看 SDK 核心 API
最后从测试反观 SDK 的核心 API 面,帮助理解测试为什么这样写:
CodebuffClient(sdk/src/client.ts):构造函数要求apiKey(也可从CODEBUFF_API_KEY环境变量自动读取),核心方法为run(options)与checkConnection();RunOptions(sdk/src/run.ts):除agent、prompt外,支持previousRun(多轮续聊)、content(多模态文本+图片)、signal(AbortSignal 中止)、params(自定义 Agent 的结构化输入)、extraToolResults、drainSteeringMessages(运行中注入消息)等高级能力;CodebuffClientOptions(sdk/src/run.ts):projectFiles、knowledgeFiles、agentDefinitions、customToolDefinitions、maxAgentSteps、handleEvent、handleStreamChunk、overrideTools(替换内置工具实现)、terminalCommandBroker等,均可作为客户端级或单次 run 级配置传入。
测试中常用的agent标识符格式为publisher/agentId@version(如codebuff/base@latest、codebuff/base2@latest),也可直接传AgentDefinition对象,这在前述自定义 Agent 测试中已有充分演示。
十、总结:一套"可本地运行、可 CI 验证、可上手复刻"的测试体系
sdk/e2e测试体系最值得借鉴的设计在于三点:
- Mock 与真实调用的无缝切换:通过
RUN_CODEBUFF_E2E环境变量决定走 Mock(本地确定性运行)还是真实 API(CI 全链路验证),getApiKey()与setupE2eMocks()让测试代码本身无需任何分支; - 统一的断言中枢:
EventCollector收敛了事件收集、类型过滤、顺序校验、文本拼接等全部断言能力,使测试用例极度精简; - 从机制到行为的全覆盖:集成测试锁死事件类型与顺序契约,E2E 测试验证多轮对话、并发流、子 Agent 流、自定义工具等真实工作流,示例脚本则成为 SDK 使用者的上手教材。
如果你正准备为 Codebuff SDK 编写集成测试,建议直接从 sdk/e2e/utils/event-collector.ts 与 sdk/e2e/utils/get-api-key.ts 这两个工具类入手,再参照 event-ordering.integration.test.ts 与 weather-agent.e2e.test.ts 的模板搭建自己的用例——这套分层清晰、可复用的测试结构,值得每个接入 SDK 的项目直接借鉴。
【免费下载链接】freebuffThe free coding agent项目地址: https://gitcode.com/GitHub_Trending/cod/freebuff
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考