TestingStrategySkill testing-strategy

这是一个全面的测试策略,使用Vitest和Playwright工具进行单元、集成和E2E测试,包括最佳实践和覆盖目标。

测试 0 次安装 0 次浏览 更新于 3/2/2026

测试策略技能

目标

使用现代工具(Vitest, Playwright)实施全面的测试策略,涵盖单元、集成和E2E测试,并设定清晰的覆盖目标和最佳实践。

何时使用此技能

自动调用当:

  • 用户提到“测试”、“测试”、“覆盖”、“TDD”、“E2E”
  • 设置新项目
  • 添加新功能(需要测试)
  • 调试测试失败
  • 提高测试覆盖率

测试金字塔

        /\
       /E2E\         少量,慢,昂贵
      /------\
     /  集成 \      一些,中等速度
    /----------\
   / 单元测试 \   许多,快速,便宜
  /--------------\

分布

  • 70% 单元测试 - 快速,隔离,便宜
  • 20% 集成测试 - 中等速度,测试交互
  • 10% E2E测试 - 慢,昂贵,关键用户流程

测试类型

1. 单元测试(Vitest)

是什么:测试隔离中的单个函数/组件

工具:Vitest, React Testing Library

覆盖目标:80%+

设置

npm install -D vitest @vitest/ui @testing-library/react @testing-library/jest-dom

配置vitest.config.ts):

import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './tests/setup.ts',
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      exclude: ['node_modules/', 'tests/'],
      thresholds: {
        lines: 80,
        functions: 80,
        branches: 75,
        statements: 80
      }
    }
  }
})

示例Button.test.tsx):

import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect, vi } from 'vitest'
import { Button } from './Button'

describe('Button', () => {
  it('渲染文本', () => {
    render(<Button>点击我</Button>)
    expect(screen.getByText('点击我')).toBeInTheDocument()
  })

  it('点击时调用onClick', () => {
    const handleClick = vi.fn()
    render(<Button onClick={handleClick}>点击</Button>)
    fireEvent.click(screen.getByText('点击'))
    expect(handleClick).toHaveBeenCalledOnce()
  })

  it('当disabled属性为true时禁用', () => {
    render(<Button disabled>禁用</Button>)
    expect(screen.getByRole('button')).toBeDisabled()
  })
})

命令

npm run test              # 运行所有测试
npm run test:watch        # 监视模式
npm run test:ui           # 可视化UI
npm run test:coverage     # 带覆盖率

2. 集成测试(Vitest)

是什么:测试组件交互、API调用、状态管理

示例UserProfile.test.tsx):

import { render, screen, waitFor } from '@testing-library/react'
import { describe, it, expect, vi } from 'vitest'
import { UserProfile } from './UserProfile'

// 模拟API
vi.mock('./api', () => ({
  fetchUser: vi.fn(() => Promise.resolve({
    id: 1,
    name: 'John Doe',
    email: 'john@example.com'
  }))
}))

describe('UserProfile集成', () => {
  it('获取并显示用户数据', async () => {
    render(<UserProfile userId="1" />)
    
    expect(screen.getByText('加载中...')).toBeInTheDocument()
    
    await waitFor(() => {
      expect(screen.getByText('John Doe')).toBeInTheDocument()
      expect(screen.getByText('john@example.com')).toBeInTheDocument()
    })
  })
})

3. E2E测试(Playwright)

是什么:在真实浏览器中测试完整的用户流程

工具:Playwright

覆盖目标:仅关键路径

设置

npm install -D @playwright/test
npx playwright install

配置playwright.config.ts):

import { defineConfig, devices } from '@playwright/test'

export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'mobile',
      use: { ...devices['iPhone 13'] },
    },
  ],
  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
})

示例e2e/auth.spec.ts):

import { test, expect } from '@playwright/test'

test.describe('认证流程', () => {
  test('用户可以注册并登录', async ({ page }) => {
    // 注册
    await page.goto('/signup')
    await page.fill('[name="email"]', 'test@example.com')
    await page.fill('[name="password"]', 'SecurePass123!')
    await page.click('button[type="submit"]')
    
    // 应重定向到仪表板
    await expect(page).toHaveURL(/\/dashboard/)
    await expect(page.locator('h1')).toContainText('欢迎')
    
    // 登出
    await page.click('[aria-label="用户菜单"]')
    await page.click('text=登出')
    
    // 应重定向到首页
    await expect(page).toHaveURL('/')
    
    // 重新登录
    await page.goto('/login')
    await page.fill('[name="email"]', 'test@example.com')
    await page.fill('[name="password"]', 'SecurePass123!')
    await page.click('button[type="submit"]')
    
    await expect(page).toHaveURL(/\/dashboard/)
  })
})

命令

npx playwright test                    # 运行所有E2E
npx playwright test --ui               # 交互模式
npx playwright test --headed           # 显示浏览器
npx playwright test --project=chromium # 特定浏览器
npx playwright show-report             # 查看最后报告

测试最佳实践

AAA模式

// 安排
const user = { id: 1, name: 'John' }
const mockFetch = vi.fn()

// 行动
const result = await fetchUser(mockFetch, 1)

// 断言
expect(result).toEqual(user)
expect(mockFetch).toHaveBeenCalledWith('/api/users/1')

测试命名

// 好的:描述性,解释什么和何时
it('当API返回404时显示错误消息', () => {})
it('当表单无效时禁用提交按钮', () => {})

// 坏的:模糊,不清晰
it('有效', () => {})
it('测试1', () => {})

每个测试一个断言(指南)

// 优先聚焦测试
it('渲染用户名', () => {
  render(<User name="John" />)
  expect(screen.getByText('John')).toBeInTheDocument()
})

it('渲染用户邮箱', () => {
  render(<User email="john@example.com" />)
  expect(screen.getByText('john@example.com')).toBeInTheDocument()
})

// 超过复杂测试
it('渲染用户数据', () => {
  // 多个不相关的断言
})

模拟外部依赖

// 模拟API调用
vi.mock('./api', () => ({
  fetchUser: vi.fn()
}))

// 模拟环境
vi.stubEnv('API_URL', 'http://test-api.com')

// 模拟计时器
vi.useFakeTimers()
const now = new Date('2024-01-01')
vi.setSystemTime(now)

覆盖策略

测试什么

要测试

  • 业务逻辑
  • 边缘情况和错误处理
  • 用户交互
  • API集成
  • 状态管理
  • 验证逻辑
  • 关键用户流程(E2E)

不要测试

  • 第三方库
  • 框架内部
  • 常量
  • 简单的getter/setter
  • 生成的代码

覆盖目标

最低

  • 行:80%
  • 函数:80%
  • 分支:75%
  • 语句:80%

理想

  • 关键路径:100%
  • 业务逻辑:95%+
  • UI组件:85%+
  • 工具:90%+

运行覆盖率

npm run test:coverage

# 在浏览器中查看
open coverage/index.html

测试工作流程

1. TDD方法(推荐)

1. 编写失败的测试
2. 编写最小代码通过
3. 重构
4. 重复

2. 测试后(实用)

1. 实现功能
2. 编写测试
3. 实现80%+覆盖率
4. 有信心地重构

3. 提交前测试

# 提交前运行
npm run test:quick        # 快速单元测试
npm run lint
npm run typecheck

# 提交前推送
npm run test             # 所有单元/集成
npm run test:coverage    # 验证覆盖率

# 部署前运行
npm run test:e2e         # 完整E2E套件

测试组织

目录结构

src/
├── components/
│   ├── Button/
│   │   ├── Button.tsx
│   │   ├── Button.test.tsx      # 共同定位
│   │   └── Button.stories.tsx   # Storybook
│   └── ...
tests/
├── setup.ts                      # 测试设置
├── utils/                        # 测试工具
│   ├── renderWithProviders.tsx  # 自定义渲染
│   └── mockData.ts              # 测试夹具
└── __mocks__/                   # 全局模拟
e2e/
├── auth.spec.ts
├── checkout.spec.ts
└── fixtures/                    # E2E测试数据

命名约定

  • 单元/集成:*.test.ts*.test.tsx
  • E2E:*.spec.ts
  • 设置:setup.ts, vitest.config.ts

持续集成

GitHub Actions示例

name: Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 18
          cache: 'npm'
      
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
      - run: npm run test:coverage
      
      - name: 上传覆盖率
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/coverage-final.json
  
  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npm run build
      - run: npx playwright test
      
      - uses: actions/upload-artifact@v3
        if: always()
        with:
          name: playwright-report
          path: playwright-report/

调试测试

Vitest

# 运行单个测试文件
npm run test -- Button.test.tsx

# 运行匹配模式的测试
npm run test -- --grep "Button渲染"

# 在VS Code中调试
# 添加断点,按F5

Playwright

# 调试模式
npx playwright test --debug

# 特定测试
npx playwright test auth.spec.ts --debug

# 跟踪查看器
npx playwright show-trace trace.zip

常见测试模式

测试异步代码

it('获取用户数据', async () => {
  const { result } = renderHook(() => useUser(1))
  
  await waitFor(() => {
    expect(result.current.data).toEqual({ id: 1, name: 'John' })
  })
})

测试错误状态

it('当获取失败时显示错误', async () => {
  vi.mocked(fetchUser).mockRejectedValue(new Error('网络错误'))
  
  render(<UserProfile userId="1" />)
  
  await waitFor(() => {
    expect(screen.getByText(/error/i)).toBeInTheDocument()
  })
})

测试表单

it('提交有效数据的表单', async () => {
  const handleSubmit = vi.fn()
  render(<LoginForm onSubmit={handleSubmit} />)
  
  await userEvent.type(screen.getByLabelText('邮箱'), 'test@example.com')
  await userEvent.type(screen.getByLabelText('密码'), 'password123')
  await userEvent.click(screen.getByRole('button', { name: /提交/i }))
  
  expect(handleSubmit).toHaveBeenCalledWith({
    email: 'test@example.com',
    password: 'password123'
  })
})

与其他技能集成

  • quality-gates - 将测试作为质量检查
  • git-workflow - 提交前钩子中的测试
  • codebase-analysis - 识别未测试的代码

package.json脚本

{
  "scripts": {
    "test": "vitest",
    "test:watch": "vitest --watch",
    "test:ui": "vitest --ui",
    "test:coverage": "vitest --coverage",
    "test:e2e": "playwright test",
    "test:e2e:ui": "playwright test --ui",
    "test:e2e:headed": "playwright test --headed",
    "test:all": "npm run test:coverage && npm run test:e2e"
  }
}

版本历史

  • 1.0.0 (2025-01-03):初始测试策略,使用Vitest和Playwright