# 研发流水线 — 测试分层策略 --- ## 测试金字塔 ``` ╱╲ ╱ 冒烟 ╲ ← 5-10% (部署后关键路径验证) ╱────────╲ ╱ 集成测试 ╲ ← 20-30% (API/服务间交互) ╱──────────────╲ ╱ 单元测试 ╲ ← 60-70% (函数/类级别) ╱──────────────────╲ ``` --- ## 各层测试规范 ### 单元测试 **目标:** 验证单个函数/类的行为正确性 **覆盖率要求:** 行覆盖 ≥ 70%,函数覆盖 ≥ 80% **测试Agent生成规则:** 1. 只看接口签名+文档+验收标准,不看实现细节(黑盒测试优先) 2. 必须覆盖:正常路径、边界值(空/null/最大/最小)、异常路径 3. 每个测试函数只验证一个行为点 4. 测试命名格式:`test_<功能>_<场景>_<预期结果>` ### 集成测试 **目标:** 验证模块间/服务间交互正确性 **覆盖率要求:** 所有API接口100%覆盖 **测试Agent生成规则:** 1. 每个API端点至少:1个成功用例 + 1个参数校验失败 + 1个权限失败 2. 测试数据库/外部服务交互的完整流程 3. 使用真实的数据库(SQLite内存模式)或合理的Mock ### 冒烟测试 **目标:** 部署后快速验证核心功能可用 **覆盖率要求:** 覆盖所有核心用户路径 **测试Agent生成规则:** 1. 从验收标准(AC)中提取关键路径 2. 只验证"能不能用",不验证边界条件 3. 执行时间 < 30秒 4. 对于前端项目:启动服务 → 访问关键页面 → 验证无崩溃 --- ## 测试质量保障 ### 独立性原则(参考AgentCoder论文 arXiv:2312.13010) - 测试Agent(minimax-m3)与编码Agent(deepseek-v4-pro)使用不同模型 - 测试Agent的prompt中**不包含**编码Agent的prompt和实现思路 - 测试Agent只接收:接口文档 + 验收标准 + 代码文件(只看签名,不解释逻辑) ### 测试Agent的Context模板 ``` 你是一个QA测试工程师。根据以下信息编写测试: ## 需求验收标准 {acceptance_criteria} ## 接口/函数签名 {api_signatures} ## 项目测试约定 - 框架: {test_framework} - 运行命令: {test_command} - 测试目录: {test_directory} ## 规则 1. 优先编写能发现bug的测试(边界值、异常路径) 2. 不要写"同义反复"测试(只是重复实现逻辑) 3. 每个测试必须有明确的断言 4. 测试之间相互独立,无执行顺序依赖 5. 编写完成后运行全部测试并输出结果 ``` --- ## 覆盖率阈值 | 项目规模 | 行覆盖 | 分支覆盖 | 函数覆盖 | |---------|--------|---------|---------| | 大型(新项目) | ≥ 70% | ≥ 60% | ≥ 80% | | 中型(新功能) | ≥ 60% | ≥ 50% | ≥ 75% | | 小型(bug修复) | 修改行100%覆盖 | — | — | --- ## 前端项目特殊规则 前端项目(React/Vue/Next.js等)的测试策略有所不同: | 层级 | 内容 | 工具建议 | |------|------|---------| | 单元 | 组件渲染测试、工具函数测试 | Jest + Testing Library | | 集成 | 页面级交互测试 | Playwright / Cypress | | 冒烟 | 构建+启动+关键页面可达+无JS错误 | curl + Playwright | **前端冒烟测试最低要求:** 1. `npm run build` 成功(无编译错误) 2. 构建产物包含所有预期路由(检查 `.next/routes-manifest.json`) 3. 关键页面路由可访问(无404) 4. 浏览器控制台无未捕获错误 **⚠️ 陷阱:不要在vitest中spawn服务器** 在测试中用 `spawn('npm', ['run', 'start'])` 启动Next.js再fetch会超时(Next.js启动慢)且进程清理不干净。替代方案:解析build产物中的 `routes-manifest.json`,验证静态路由和动态路由是否完整。示例: ```typescript it('should have all expected routes after build', () => { const routesManifest = JSON.parse( fs.readFileSync('.next/routes-manifest.json', 'utf-8') ) const dynamicRoutes = routesManifest.dynamicRoutes?.map(r => r.page) || [] const staticRoutes = routesManifest.staticRoutes?.map(r => r.page) || [] expect(staticRoutes).toContain('/') expect(dynamicRoutes).toContain('/departments/[id]') }) ```