Skip to content

测试覆盖率与 CI 集成

AI 生成代码的覆盖率标准

传统项目通常设定 70-80% 的覆盖率门槛。对于 AI 辅助开发的项目,这个标准应该更高

原因:

  1. AI 生成测试的成本极低,没有理由接受低覆盖率
  2. AI 可能生成逻辑正确但有细微 bug 的代码,更高覆盖率能捕获更多问题
  3. AI 代码的可信度需要测试来背书,覆盖率是最直观的质量信号

推荐覆盖率目标

项目类型语句覆盖率分支覆盖率函数覆盖率
业务核心模块90%+85%+95%+
工具函数库95%+90%+100%
API 路由层85%+80%+90%+
UI 组件70%+65%+80%+
整体项目80%+75%+85%+

覆盖率配置

Vitest 覆盖率配置

typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    coverage: {
      provider: 'v8',           // 或 'istanbul'
      reporter: ['text', 'html', 'lcov', 'json-summary'],
      reportsDirectory: './coverage',

      // 覆盖率门槛
      thresholds: {
        statements: 80,
        branches: 75,
        functions: 85,
        lines: 80,
      },

      // 排除不需要覆盖的文件
      exclude: [
        'node_modules/**',
        'dist/**',
        '**/*.d.ts',
        '**/*.config.*',
        '**/test-helpers/**',
        'coverage/**',
        '.vitepress/**',
      ],

      // 包含的文件范围
      include: ['src/**/*.{ts,tsx}'],
    },
  },
})

运行覆盖率检查:

bash
# 运行测试并生成覆盖率报告
npx vitest run --coverage

# 如果覆盖率低于阈值,命令会以非零状态退出

CI 流水线设计

推荐的 CI 检查顺序

代码推送 / PR 创建

1. 代码检查(Lint + TypeScript 类型检查)  ← 最快,先失败先知道

2. 单元测试 + 覆盖率检查                   ← 核心质量门槛

3. 集成测试                                ← 需要服务依赖

4. E2E 测试(可选,仅 main 分支)          ← 最慢,按需运行

✓ 全部通过 → 允许合并

GitHub Actions 配置

基础 CI 配置

yaml
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  # ─── 代码质量检查 ───────────────────────────────────────────
  lint:
    name: Lint & Type Check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run ESLint
        run: npm run lint

      - name: TypeScript type check
        run: npm run type-check

  # ─── 单元测试 + 覆盖率 ──────────────────────────────────────
  unit-tests:
    name: Unit Tests
    runs-on: ubuntu-latest
    needs: lint
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run unit tests with coverage
        run: npm run test:coverage

      - name: Upload coverage report
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage/
          retention-days: 7

      # 在 PR 评论中显示覆盖率摘要
      - name: Coverage comment on PR
        if: github.event_name == 'pull_request'
        uses: davelosert/vitest-coverage-report-action@v2
        with:
          json-summary-path: coverage/coverage-summary.json

  # ─── 集成测试 ───────────────────────────────────────────────
  integration-tests:
    name: Integration Tests
    runs-on: ubuntu-latest
    needs: unit-tests
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: testdb
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run database migrations
        run: npm run db:migrate
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/testdb

      - name: Run integration tests
        run: npm run test:integration
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/testdb
          NODE_ENV: test

E2E 测试配置(仅 main 分支)

yaml
  # ─── E2E 测试(仅在 main 分支运行)─────────────────────────
  e2e-tests:
    name: E2E Tests
    runs-on: ubuntu-latest
    needs: integration-tests
    if: github.ref == 'refs/heads/main'

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright browsers
        run: npx playwright install --with-deps chromium

      - name: Build application
        run: npm run build

      - name: Start application
        run: npm run start &
        env:
          NODE_ENV: test

      - name: Wait for server
        run: npx wait-on http://localhost:3000 --timeout 30000

      - name: Run E2E tests
        run: npm run test:e2e

      - name: Upload Playwright report
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 3

质量门禁设置

分支保护规则

在 GitHub 仓库设置中配置(Settings → Branches → Branch protection rules):

保护 main 分支:
✓ Require status checks to pass before merging
  - CI / Lint & Type Check
  - CI / Unit Tests
  - CI / Integration Tests
✓ Require branches to be up to date before merging
✓ Require pull request reviews before merging (1 reviewer)
✓ Dismiss stale pull request approvals when new commits are pushed

package.json 脚本配置

json
{
  "scripts": {
    "test": "vitest",
    "test:run": "vitest run",
    "test:coverage": "vitest run --coverage",
    "test:integration": "vitest run --config vitest.integration.config.ts",
    "test:e2e": "playwright test",
    "lint": "eslint src --ext .ts,.tsx",
    "type-check": "tsc --noEmit"
  }
}

覆盖率报告阅读指南

运行 npm run test:coverage 后,终端输出示例:

 % Coverage report from v8
-------------------|---------|----------|---------|---------|
File               | % Stmts | % Branch | % Funcs | % Lines |
-------------------|---------|----------|---------|---------|
All files          |   84.23 |    79.41 |   88.57 |   84.23 |
 src/              |         |          |         |         |
  discount.ts      |     100 |      100 |     100 |     100 |
  order.ts         |   91.67 |    83.33 |     100 |   91.67 |
  user.service.ts  |   72.34 |    65.00 |   76.92 |   72.34 |  ← 需要关注
-------------------|---------|----------|---------|---------|

如何处理低覆盖率文件:

  1. 查看 coverage/index.html 找到未覆盖的具体行
  2. 判断:是真的缺少测试,还是死代码?
  3. 如果是死代码,删除它;如果是缺少测试,补充

不必追求 100% 覆盖率的情况:

  • 错误处理的 fallback 分支(极难触发的防御性代码)
  • 第三方库的类型定义文件
  • 框架生成的脚手架代码

本地开发工作流建议

bash
# 监视模式:修改代码时自动重跑相关测试
npx vitest

# 提交前手动验证
npm run test:coverage && npm run lint && npm run type-check

# 使用 git hook 自动化(pre-commit)
# 安装 husky
npm install --save-dev husky
npx husky init

# .husky/pre-commit
npm run lint && npm run type-check

# .husky/pre-push
npm run test:coverage

总结

CI 集成把"测试是否通过"从个人习惯变成了团队硬性约束。对于 AI 辅助开发,这一点尤为重要:AI 生成的代码需要自动化验证作为最后一道防线。推荐的起步配置是:PR 必须通过单元测试 + 覆盖率检查,后续再逐步加入集成测试和 E2E 测试。