出处:掘金

原作者:金泽宸


前端测试体系不是“测试团队的事”,而是让架构可持续、业务可验证、上线有信心的核心保障

写在前面

架构师不是只管搭框架,更要构建一套“可被验证、可被信任”的测试体系

很多团队的测试现状是这样的:

  • 测试都靠点一遍
  • 上线后才发现按钮没绑事件
  • 重构组件后多个页面挂了没人发现
  • PR 代码合并了,线上崩了

解决这些问题,必须搭建一个可落地、可自动化、覆盖从组件到页面的测试体系

测试金字塔结构(前端视角)

类型说明工具推荐目的
单元测试函数、组件渲染、交互逻辑Vitest / Jest测组件、函数
集成测试组件组合、状态流、API 调用@testing-library/vue组件交互联动
E2E 测试覆盖用户行为流,发现集成级 bugCypress / Playwright模拟真实用户操作

技术选型建议(Vue 项目)

模块工具
测试框架Vitest(更快、更现代)
渲染测试@testing-library/vue
模拟请求msw(Mock Service Worker)
端到端测试Cypress / Playwright
Mock 工具vi.fn / sinon / faker

组件单元测试示例

UserCard.vue 为例:

<template>
  <div class="user-card">
    <h3>{{ user.name }}</h3>
    <button @click="onFollow">关注</button>
  </div>
</template>
 
<script setup>
const props = defineProps({ user: Object, onFollow: Function })
</script>

单测:

import { render, fireEvent } from '@testing-library/vue'
import UserCard from './UserCard.vue'
 
test('渲染用户名 + 点击关注', async () => {
  const user = { name: '张三' }
  const fn = vi.fn()
 
  const { getByText } = render(UserCard, {
    props: { user, onFollow: fn },
  })
 
  getByText('张三')
  await fireEvent.click(getByText('关注'))
  expect(fn).toHaveBeenCalled()
})

集成测试示例

以表单组合为例(组件交互 & 状态联动):

<template>
  <Form @submit="handle">
    <Input v-model="name" />
    <Button type="submit">提交</Button>
  </Form>
</template>

集成测试:

test('输入后提交按钮触发', async () => {
  const { getByRole } = render(MyForm)
  const input = getByRole('textbox')
  const btn = getByRole('button')
 
  await fireEvent.update(input, 'hello')
  await fireEvent.click(btn)
 
  // 断言提交行为
})

E2E 测试流程(Cypress)

安装 Cypress:

npm install -D cypress
npx cypress open

示例测试文件:

describe('登录流程', () => {
  it('输入用户名密码并跳转首页', () => {
    cy.visit('/login')
    cy.get('#username').type('admin')
    cy.get('#password').type('123456')
    cy.get('button').click()
    cy.url().should('include', '/home')
  })
})

E2E 自动化优势:

  • 真浏览器环境 + 网络模拟
  • 多端适配验证
  • 快照 + 录像记录(CI/CD 用)

Mock 请求工具(MSW)

// mocks/handlers.ts
import { rest } from 'msw'
 
export const handlers = [
  rest.get('/api/user', (req, res, ctx) => {
    return res(ctx.json({ name: '张三' }))
  }),
]

配合组件测试:

import { setupServer } from 'msw/node'
import { handlers } from './handlers'
 
const server = setupServer(...handlers)
 
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

测试目录与命名规范建议

src/
├── components/
│   └── UserCard.vue
│   └── __tests__/
│       └── UserCard.test.ts
├── pages/
│   └── Home.vue
├── tests/
│   ├── e2e/
│   │   └── login.spec.ts
│   ├── mocks/
│   │   └── handlers.ts

建议命名:组件名.test.ts 或 组件名.spec.ts

测试集成进 CI/CD

在 GitHub Actions 添加:

- name: 🧪 运行单元测试
  run: npm run test
 
- name: 🧪 运行 E2E 测试(Cypress)
  uses: cypress-io/github-action@v5
  with:
    start: npm run dev
    wait-on: 'http://localhost:5173'

成功后打包部署,失败自动阻断发布流程

测试覆盖范围建议

类型推荐覆盖点
函数工具类所有核心逻辑分支
UI 组件展示正确、交互逻辑、边界行为
复合组件状态联动、异常状态、表单校验
页面流程登陆、跳转、错误提示、弹窗流程
权限/路由不同用户是否看到按钮、页面

测试文化落地建议

目标落地方式
强制新组件带测试新建组件 CLI 自动生成 test 文件
每次构建检查覆盖率使用 vitest --coverage,CI 中设最低阈值
开发阶段自动执行使用 vitest --watch 实时反馈
拉新 PR 自动测试Actions + 报告输出(测试截图、失败栈)
低风险回归所有核心模块都具备覆盖率 70%+