从入门到放弃再到成功:一个前端程序员的后端 API 调试血泪史
序言:这是一个真实的技术调试故事,记录了一个主要从事前端开发的程序员,在 AI 助手的帮助下,如何一步步解决 Chrome 扩展后端 API 的集成测试问题。从最初的 18/31 测试失败,到最终的 31/31 全部通过,这个过程充满了挫折、学习和成长。
我叫小李,是一名有着3年经验的前端开发工程师。最近在开发一个基于 Plasmo 框架的 Chrome 扩展,前端部分我驾轻就熟,但后端 API 对我来说还是个相对陌生的领域。
项目采用了比较现代的技术栈:
一切看起来都很美好,直到我开始写集成测试...
那是一个周五的下午,我刚写完用户认证相关的 API 接口。手动测试一切正常:
我满心欢喜地跑了一下集成测试,结果...
❯ npm run test:integration
FAIL Tests failed: 18/31
**18个测试失败!**我当场懵了。手动用 Postman 测试明明都正常,为什么自动化测试全挂了?
最让我困惑的是错误信息:
Expected status 201, got 400. Response: {"success":false,"message":"用户名为必需字段","status":400}
用户名是必需字段?但是我明明在测试中生成了用户名啊!我检查了测试代码:
const testUser = generateTestUser()
console.log('Generated user:', testUser) // { username: 'test_123', email: '[email protected]', password: 'password123' }
const response = await client.register(testUser)
数据明明都有,为什么服务器收到的是 undefined?
绝望之中,我决定向 AI 求助。我详细描述了问题:
"我的 Chrome 扩展 API 集成测试失败了,18/31 个测试不通过。奇怪的是,手动测试都正常,但自动化测试时服务器总是收到 undefined 的字段值。使用的是 Hono + TypeScript + Zod + Vitest,你能帮我分析一下可能的原因吗?"
AI 的回答让我眼前一亮:
"这个问题很可能是测试环境配置问题。在 Node.js 环境中运行测试时,可能缺少 fetch polyfill,或者请求头配置有问题。让我们先检查 Vitest 配置..."
AI 指导我检查 vitest.config.ts
文件,我发现确实没有配置 setupFiles:
// vitest.config.ts - 修复前
export default defineConfig({
test: {
environment: 'node',
// 缺少 setupFiles 配置!
}
})
原来 Node.js 环境下需要手动引入 fetch polyfill!AI 建议我:
undici
包tests/setup.ts
文件npm install --save-dev undici
// tests/setup.ts
import { fetch, Headers, Request, Response } from 'undici';
global.fetch = fetch as any;
global.Headers = Headers as any;
global.Request = Request as any;
global.Response = Response as any;
// vitest.config.ts - 修复后
export default defineConfig({
test: {
environment: 'node',
setupFiles: ['./tests/setup.ts'] // 添加这行
}
})
我兴奋地重新运行测试,结果...还是失败!但这次错误信息变了:
TypeError: Cannot read properties of undefined (reading 'headers')
这说明至少 fetch 调用生效了,但还有其他问题。
AI 继续帮我分析,发现了 API 客户端中的一个细微但致命的问题:
// api-client.ts - 有问题的版本
async request(endpoint: string, options: RequestOptions = {}) {
const config: RequestInit = {
method: options.method || 'GET',
...options.headers, // 这里有问题!当 headers 为 undefined 时会出错
body: options.body ? JSON.stringify(options.body) : undefined,
}
}
AI 解释说:"当 options.headers
为 undefined 时,...undefined
会导致问题。应该使用 ...(options.headers || {})
来确保始终展开一个对象。"
修复后的代码:
// api-client.ts - 修复后
async request(endpoint: string, options: RequestOptions = {}) {
const config: RequestInit = {
method: options.method || 'GET',
body: options.body ? JSON.stringify(options.body) : undefined,
headers: {
'Content-Type': 'application/json',
...(options.headers || {}) // 修复:确保 headers 不为 undefined
}
}
}
再次运行测试,这次有了重大突破!很多测试开始返回 201 Created 状态码,说明请求终于能正确发送到服务器了。
但是,新的问题出现了...
虽然请求问题解决了,但现在测试失败的原因变成了响应验证问题:
ZodError: [
{
"code": "invalid_type",
"expected": "object",
"received": "undefined",
"path": ["message"],
"message": "Required"
}
]
AI 帮我分析:"现在问题是响应结构与测试预期不匹配。服务器返回的数据缺少某些字段,或者字段结构与 Zod schema 定义不一致。"
我开始逐个检查 API 响应:
/auth/me
接口的问题
测试期望的响应结构:
{
success: true,
message: string,
data: {
user: UserObject
}
}
但实际的服务器响应:
{
success: true,
data: UserObject, // 缺少包装对象
timestamp: "2024-01-01T00:00:00.000Z"
// 缺少 message 字段
}
AI 指导我修复服务器端代码:
// 修复前
return c.json({
success: true,
data: user,
timestamp: new Date().toISOString()
})
// 修复后
return c.json({
success: true,
data: {
user // 包装在对象中
},
message: "获取用户信息成功", // 添加 message
timestamp: new Date().toISOString()
})
类似的问题也出现在用户名和邮箱可用性检查接口上。AI 建议我统一添加 message 字段:
// 用户名可用性检查 - 修复后
return c.json({
success: true,
data: {
username,
available: !exists
},
message: "用户名可用性检查完成", // 新增
timestamp: new Date().toISOString()
})
但修复这些之后,还有一个更复杂的问题等着我...
在修复测试的过程中,AI 发现了一个我在需求理解上的错误。我把 receiveOfficialMessages
当作了一个纯 UI 设置,但实际上它应该是:
AI 耐心地为我解释了这个概念:
"在实际应用中,
receiveOfficialMessages
这类字段通常既是用户属性,也是用户设置。它需要存储在用户表中,因为它影响系统行为(比如是否发送邮件通知),而不仅仅是 UI 偏好设置。"
首先需要修改用户 Schema:
// user.ts - 修复前
export const UserSchema = z.object({
id: z.string().uuid(),
username: z.string(),
email: z.string().email(),
isSponsored: z.boolean(),
settings: UserSettingsSchema, // receiveOfficialMessages 错误地放在这里
createdAt: z.string(),
updatedAt: z.string()
})
// user.ts - 修复后
export const UserSchema = z.object({
id: z.string().uuid(),
username: z.string(),
email: z.string().email(),
isSponsored: z.boolean(),
receiveOfficialMessages: z.boolean(), // 提升到用户级别
settings: UserSettingsSchema,
createdAt: z.string(),
updatedAt: z.string()
})
最复杂的部分是修改 updateUserSettings
方法。AI 指导我实现了一个动态 SQL 更新的方案:
// AuthService.updateUserSettings - 修复后
async updateUserSettings(userId: string, settings: any) {
// 分离用户级设置和UI设置
const { receiveOfficialMessages, ...uiSettings } = settings
// 动态构建查询
let updateQuery = "UPDATE users SET settings = ?, updated_at = ?"
const params = [JSON.stringify(uiSettings), new Date().toISOString()]
// 如果包含用户级设置,添加到查询中
if (receiveOfficialMessages !== undefined) {
updateQuery += ", receive_official_messages = ?"
params.push(receiveOfficialMessages)
}
updateQuery += " WHERE id = ?"
params.push(userId)
await this.db.prepare(updateQuery).bind(...params).run()
}
AI 解释说:"这种方法的好处是,可以灵活地更新不同类型的设置,而不需要写很多重复的代码。"
经过前面的大修改,测试通过率已经从 18/31 提升到了 30/31,只剩下最后一个测试失败:
Expected error message to contain "token", but got "无效的认证令牌"
AI 指出这是一个简单的字符串匹配问题:
// 修复前
throw new HTTPException(401, { message: "无效的认证令牌" })
// 修复后
throw new HTTPException(401, { message: "无效的token" })
看起来微不足道,却是自动化测试的关键点。AI 说:"在编写测试时,错误消息的一致性很重要。这不仅仅是为了通过测试,也是为了给前端提供一致的用户体验。"
还有一个问题是用户名和邮箱的格式验证。测试中生成的用户名 available_${Date.now()}
长度超过了20个字符的限制。
AI 建议我在服务器端添加完整的输入验证:
// 用户名验证
if (username.length < 3 || username.length > 20 || !/^[a-zA-Z0-9_]+$/.test(username)) {
throw new HTTPException(400, { message: "用户名格式不正确" })
}
// 邮箱验证
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(email)) {
throw new HTTPException(400, { message: "邮箱格式不正确" })
}
同时修改测试中的用户名生成策略:
// 修复前:用户名太长
const newUsername = `available_${Date.now()}`
// 修复后:控制长度
const newUsername = `avail${Date.now().toString().slice(-8)}`
经过几轮修复,我再次运行测试:
❯ npm run test:integration
✓ tests/integration/basic.test.ts (11) 334ms
✓ tests/integration/auth.test.ts (20) 647ms
Test Files 2 passed (2)
Tests 31 passed (31)
31/31 全部通过!
我激动得差点从椅子上跳起来。从最初的 18/31 失败,到现在的 31/31 全部通过,这个过程虽然充满挫折,但收获巨大。
AI 帮我总结了这次调试的关键收获:
这次经历让我深刻体会到了前端和后端开发的区别:
前端思维:
后端思维:
AI 在这个过程中就像一个经验丰富的后端导师,不仅帮我解决具体问题,还帮我理解背后的原理。
以前我觉得写测试很麻烦,手动测试不是挺好的吗?但这次经历让我认识到:
在这个过程中,AI 不仅仅是一个代码生成工具,更像是:
但AI也不是万能的,它需要我提供准确的问题描述和上下文信息。
从周五下午的绝望,到周一早上的胜利,这个调试过程花了我整整一个周末。虽然累,但我收获了:
最重要的是,我意识到学习是一个持续的过程。技术在快速发展,工具在不断更新,但解决问题的方法论是相通的:
现在,每当我遇到新的技术难题时,我都会想起这次调试经历。它告诉我:没有解决不了的问题,只有还没找到对的方法。
而有了AI这样的工具,我们不再是孤军奋战。它就像一个24小时在线的技术导师,帮助我们更快地学习和成长。
后记:如果你也是一名前端程序员,正在向全栈发展的路上,希望这个故事能给你一些启发。记住,每一个bug都是学习的机会,每一次调试都是成长的过程。
Keep coding, keep learning, keep growing! 🚀
项目信息: