CI 中的测试策略分层:smoke、integration 与 e2e 的执行时机
CI 中的测试策略分层:smoke、integration 与 e2e 的执行时机
CI 里跑全量 e2e 测试要 45 分钟——每次提交都跑,开发者等得比写代码还久。
一、场景痛点
你的 CI 流程在每次 push 时跑全量测试:500 个单元测试(2 分钟)+ 200 个集成测试(10 分钟)+ 50 个 e2e 测试(45 分钟)。总耗时 57 分钟。开发者一天提交 10 次,累计等待 570 分钟(9.5 小时)——比实际工作时间还长。
核心矛盾:不是所有测试都需要在每次提交时跑——smoke 测试每次跑(快),集成测试合并时跑(中等),e2e 测试发布前跑(慢)。
二、底层机制与原理剖析
2.1 测试金字塔与执行时机
2.2 smoke 测试的定义
smoke 不是"跑几个单元测试",而是"验证系统核心功能是否可用"的精选子集:
- 核心业务流程(登录→下单→支付):5 个 e2e
- 关键 API 可达性(/health、/api/v1/status):10 个集成
- 核心单元测试(数据校验、认证逻辑):50 个单元
- 总计约 65 个,耗时 3-5 分钟
三、生产级代码实现
3.1 GitHub Actions 分层测试配置
# .github/workflows/test-tiered.yaml —— 分层测试 CI 配置 name: Tiered Test Pipeline on: push: branches: ['*'] # 每次 push 跑 smoke pull_request: branches: [main] # PR merge 跑 unit + integration workflow_dispatch: # 手动触发全量 e2e jobs: # Tier 1: smoke 测试 — 每次 push 触发,3-5 分钟 smoke: runs-on: ubuntu-latest if: github.event_name == 'push' steps: - uses: actions/checkout@v4 - name: Install dependencies run: npm ci - name: Run smoke tests run: npm run test:smoke # test:smoke 脚本:只跑精选的 65 个关键测试 # 脚本定义在 package.json: # "test:smoke": "jest --testPathPattern='smoke/' --maxWorkers=4" # Tier 2: 单元 + 集成测试 — PR merge 触发,12 分钟 unit-integration: runs-on: ubuntu-latest if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 - name: Install dependencies run: npm ci - name: Run unit tests run: npm run test:unit - name: Run integration tests run: npm run test:integration # 集成测试需要数据库:用 Docker 启动临时数据库 - name: Setup test database run: docker-compose -f docker-compose.test.yaml up -d db - name: Run integration tests run: npm run test:integration - name: Teardown test database if: always() run: docker-compose -f docker-compose.test.yaml down # Tier 3: 全量 e2e 测试 — 发布前手动触发,45 分钟 e2e-full: runs-on: ubuntu-latest if: github.event_name == 'workflow_dispatch' steps: - uses: actions/checkout@v4 - name: Setup full test environment run: docker-compose -f docker-compose.test.yaml up -d - name: Run e2e tests run: npm run test:e2e # e2e 测试跑在完整环境中:所有服务 + 真实数据库 # 超时设置:单个 e2e 测试最长 5 分钟 env: TEST_TIMEOUT: 300000 - name: Teardown if: always() run: docker-compose -f docker-compose.test.yaml down3.2 smoke 测试精选脚本
// jest.smoke.config.ts —— smoke 测试配置:只跑关键路径 export default { // 只跑 smoke 目录下的测试 testPathPattern: 'smoke/', // 并行执行:4 个 worker 加速 maxWorkers: 4, // 超时:单个 smoke 测试最长 10 秒 testTimeout: 10000, // 失败策略:第一个失败就停止(smoke 测试失败 = 核心功能不可用) bail: 1, }; // smoke/api-health.test.ts —— API 可达性 smoke 测试 describe('API Smoke Tests', () => { test('Health endpoint is reachable', async () => { const response = await fetch(`${API_URL}/health`); expect(response.status).toBe(200); }); test('Core API v1 status endpoint', async () => { const response = await fetch(`${API_URL}/api/v1/status`); expect(response.status).toBe(200); }); test('Authentication endpoint works', async () => { const response = await fetch(`${API_URL}/api/v1/auth/login`, { method: 'POST', body: JSON.stringify({ username: 'test', password: 'test' }), }); expect(response.status).toBeLessThan(500); // 不期望 500 错误 }); }); // smoke/core-flow.test.ts —— 核心业务流程 smoke 测试 describe('Core Business Flow Smoke', () => { test('Login → Create Order → Pay flow', async () => { // Step 1: 登录 const loginRes = await fetch(`${API_URL}/api/v1/auth/login`, { method: 'POST', body: JSON.stringify({ username: 'smoke_test', password: 'smoke_pass' }), }); const { token } = await loginRes.json(); expect(token).toBeDefined(); // Step 2: 创建订单 const orderRes = await fetch(`${API_URL}/api/v1/orders`, { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify({ product_id: 'smoke-product', quantity: 1 }), }); const { order_id } = await orderRes.json(); expect(order_id).toBeDefined(); // Step 3: 支付 const payRes = await fetch(`${API_URL}/api/v1/payments`, { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify({ order_id, method: 'mock' }), }); expect(payRes.status).toBeLessThan(500); }); });四、边界分析与架构权衡
4.1 smoke 测试的覆盖盲区
smoke 只验证核心流程,不验证边界条件(并发冲突、数据校验、异常恢复)。如果核心流程通过但边界条件有 bug,smoke 不会发现。
对策:smoke 是"快速兜底",不是"完整验证"。完整验证靠集成测试和 e2e 测试。smoke 的目标是在 3 分钟内告诉你"核心功能是否还活着"。
4.2 e2e 测试的脆弱性
e2e 测试依赖完整环境,环境问题(数据库重启、网络抖动)会导致测试失败,但不是代码 bug。e2e 测试的失败率约 5-10% 是环境问题,不是应用问题。
对策:e2e 测试失败后自动重试一次。如果重试仍失败,人工排查是否是环境问题。
4.3 适用边界与禁用场景
- 适用:日提交 >5 次、团队 >3 人、测试总量 >200 个
- 禁用:日提交 <2 次(等 57 分钟也没关系)、测试总量 <50 个(全量跑也很快)、紧急修复时(跳过 e2e 直接发布)
五、结语
CI 测试策略的核心是分层执行:smoke 每次 commit 跑(3-5 分钟),集成测试 PR merge 时跑(12 分钟),e2e 发布前跑(45 分钟)。开发者日常等待时间从 57 分钟降到 3-5 分钟。smoke 不是"跑几个测试",而是"验证核心功能是否可用"的精选子集——5 个核心 e2e + 10 个 API 可达性 + 50 个关键单元测试。e2e 测试只在发布前跑,不在每次提交时跑。e2e 的环境脆弱性用自动重试缓解。分层策略的前提是测试总数量足够多(>200 个),否则全量跑也很快。
资料说明
本文中的协议、版本、性能、成本和行业趋势应以可核验的一手资料为准。未标注统计口径的比例、时间表和预测仅作工程讨论,不应视为行业事实。可参考 0730 资料来源索引,并在发布前将具体来源贴到对应断言之后。
