前端单元测试实战:从零开始构建可靠的测试体系

"又出线上问题了!"周一早会上,我们的技术总监语气严肃。一个简单的代码改动,却引发了一连串的问题。作为前端负责人,我深感愧疚。这已经是本月第三次类似的事故了。

回顾这些问题,我们发现一个共同点:都是因为代码改动引发了意想不到的副作用。如果有完善的单元测试,这些问题本可以在开发阶段就被发现。于是,我们决定系统性地构建前端测试体系。

现状分析

首先我们统计了一下现有的测试情况:

  • 测试覆盖率不到 20%
  • 大多是集成测试,运行时间长
  • 测试代码质量参差不齐
  • 团队缺乏测试习惯

就像一座没有安全检查的大楼,随时可能出现问题。我们需要从基础开始,建立起完整的测试防护网。

测试策略

经过团队讨论,我们制定了分层测试策略。就像建筑的地基、框架、装修一样,每一层都有其特定的职责:

typescript 复制代码
// 工具函数测试示例
describe('formatDate', () => {
  it('should format date correctly', () => {
    const date = new Date('2024-12-03')
    expect(formatDate(date)).toBe('2024-12-03')
    expect(formatDate(date, 'YYYY/MM/DD')).toBe('2024/12/03')
  })

  it('should handle invalid date', () => {
    expect(formatDate(null)).toBe('-')
    expect(formatDate(undefined)).toBe('-')
    expect(formatDate('invalid')).toBe('-')
  })
})

// React 组件测试示例
describe('Button', () => {
  it('should render children correctly', () => {
    const { getByText } = render(<Button>Click me</Button>)
    expect(getByText('Click me')).toBeInTheDocument()
  })

  it('should handle click events', () => {
    const handleClick = jest.fn()
    const { getByRole } = render(<Button onClick={handleClick}>Click me</Button>)

    fireEvent.click(getByRole('button'))
    expect(handleClick).toHaveBeenCalledTimes(1)
  })

  it('should show loading state', () => {
    const { getByRole, getByTestId } = render(<Button loading>Loading</Button>)

    expect(getByRole('button')).toBeDisabled()
    expect(getByTestId('loading-spinner')).toBeInTheDocument()
  })
})

测试工具链

我们精心挑选了一套测试工具链:

typescript 复制代码
// jest.config.ts
export default {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
    '\\.(css|less|scss)$': 'identity-obj-proxy'
  },
  collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts', '!src/**/*.stories.{ts,tsx}'],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  }
}

// jest.setup.ts
import '@testing-library/jest-dom'
import 'jest-canvas-mock'
import { server } from './src/mocks/server'

beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

测试规范

为了保证测试的质量和一致性,我们制定了测试规范:

typescript 复制代码
// 测试文件结构示例
describe('UserProfile', () => {
  // 准备测试数据
  const mockUser = {
    id: 1,
    name: 'John Doe',
    email: 'john@example.com'
  }

  // 分组测试用例
  describe('rendering', () => {
    it('should render user info correctly', () => {
      const { getByText } = render(<UserProfile user={mockUser} />)

      expect(getByText(mockUser.name)).toBeInTheDocument()
      expect(getByText(mockUser.email)).toBeInTheDocument()
    })

    it('should show loading state', () => {
      const { getByTestId } = render(<UserProfile loading />)
      expect(getByTestId('loading-spinner')).toBeInTheDocument()
    })
  })

  describe('interactions', () => {
    it('should handle edit button click', () => {
      const onEdit = jest.fn()
      const { getByRole } = render(<UserProfile user={mockUser} onEdit={onEdit} />)

      fireEvent.click(getByRole('button', { name: /edit/i }))
      expect(onEdit).toHaveBeenCalledWith(mockUser.id)
    })
  })

  describe('error handling', () => {
    it('should show error message', () => {
      const error = 'Failed to load user'
      const { getByText } = render(<UserProfile error={error} />)
      expect(getByText(error)).toBeInTheDocument()
    })
  })
})

测试自动化

我们将测试集成到了开发流程中:

typescript 复制代码
// 自动化测试脚本
const runTests = async changedFiles => {
  // 根据变更文件确定测试范围
  const testPatterns = getTestPatterns(changedFiles)

  // 运行测试
  const results = await jest.runCLI({
    selectProjects: testPatterns,
    onlyChanged: true,
    coverage: true
  })

  // 生成测试报告
  await generateReport(results)

  // 更新测试覆盖率徽章
  await updateCoverageBadge(results.coverage)
}

// Git hooks 配置
module.exports = {
  hooks: {
    'pre-commit': 'lint-staged && npm test',
    'pre-push': 'npm run test:coverage'
  }
}

实践效果

经过三个月的努力,我们取得了显著的成效:

  • 测试覆盖率提升到 85%
  • 线上问题减少了 70%
  • 代码重构更有信心
  • 团队形成了测试文化

最让我印象深刻的是一位同事的反馈:"有了测试,改代码的时候终于不用提心吊胆了。"

经验总结

前端测试就像是给代码买保险,虽然前期需要投入,但能避免更大的损失。我们的经验是:

从小处着手 - 先为核心功能写测试循序渐进 - 一步步提高覆盖率重视规范 - 建立统一的测试标准持续改进 - 不断优化测试流程

写在最后

前端测试不是可有可无的装饰,而是保证代码质量的重要手段。就像那句话说的:"测试是开发者的安全网,也是用户的保障。"

有什么问题欢迎在评论区讨论,让我们一起探讨前端测试的最佳实践!

如果觉得有帮助,别忘了点赞关注,我会继续分享更多实战经验~

相关推荐
leoZ2313 分钟前
本地跑大模型实战(七):llama.cpp 性能调优,让推理更快更省
java·人工智能·spring·生成对抗网络·语言模型·自然语言处理·llama
To_OC12 分钟前
我把《天龙八部》塞进向量数据库后,终于搞懂了 RAG 到底是个啥
人工智能·llm·agent
蜡台31 分钟前
AI Agent(智能体)入门基础教程
人工智能
Akir.weiwen1 小时前
Token 层差异:从颜色值到语义状态的三层跃迁
人工智能·设计规范
迷迭香yy1 小时前
集合竞价数据挖掘实战:用Python构建开盘信号识别系统
人工智能·python·数据挖掘
大模型momo2 小时前
Spring AI 实战:多 Agent 协作实战 —— 分工拆解复杂旅游行程任务
人工智能·spring·ai·agent·旅游
用户938515635072 小时前
从零理解 React Router v6:每一个 API 都是怎么工作的
前端·javascript·全栈
小程故事多_802 小时前
从A2C、TRPO、PPO到GRPO,强化学习策略梯度算法完整演进与大模型落地实战解析
人工智能·算法
冬奇Lab2 小时前
开源项目第176期:Better Harness — 不审查 diff,审查工作流本身,给 AI 编程 Agent 的五维评估框架
人工智能·开源·agent
冬奇Lab2 小时前
代码库知识库系列(07):混合检索 BM25 + 向量——Q8 还是失败,而且总分退步了
人工智能