前端测试:别为了100%覆盖率而写测试,那是自欺欺人

你写了测试,覆盖率100%,感觉稳了。结果上线后,用户点了个按钮,页面直接白屏。你纳闷:覆盖率不是100%吗?因为你测的都是"天气好不好",没测"会不会地震"。今天我们就来聊聊前端测试的正确姿势------怎么测才能真的有用,而不是为了指标好看写一堆废话。

前言

前端测试常走两个极端:要么完全不测,上线随缘;要么为了覆盖率,测了等于没测(比如测个1 + 1 = 2)。真正有效的测试,不是越多越好,而是该测的测,不该测的别浪费生命

今天我们用"测试金字塔"模型,帮你理清单元测试、组件测试、E2E测试的分工。看完你会知道:哪部分代码必须测,哪部分可以跳过,哪部分用哪个工具。

一、测试金字塔:三分天下,各司其职

bash 复制代码
       /\
      /E2E\      ← 少而精,关键路径
     /------\
    /集成测试\    ← 中等,组件间交互
   /----------\
  /  单元测试   \  ← 多而快,纯逻辑
 /--------------\
  • 底座(单元测试):测最小的代码单元(函数、工具类)。多、快、便宜。
  • 中层(组件测试/集成测试):测几个单元结合后的行为(比如一个表单组件提交数据)。
  • 顶层(E2E测试):模拟真实用户,测整个流程(从打开页面到点击到结果)。

比例大概是:单元测试占70%,集成测试20%,E2E 10%。不是死规定,但原则:底层测试成本低,多写;顶层测试维护成本高,只写关键路径

二、单元测试:测逻辑,不测实现细节

单元测试的目标:给定输入,输出是否正确。不关心函数内部怎么实现的,只关心结果。

适合测的

  • 纯函数(输入输出确定,无副作用)。
  • 业务规则(比如calculateDiscount(price, level))。
  • 工具函数(formatDateparseQuery)。

不适合测的(测了也白测):

  • 框架内部逻辑(React的setState、Vue的响应式------那是框架的事)。
  • 简单的getter/setter。
  • 常量定义。

工具:Jest + Vitest(Vite项目推荐Vitest)。

例子

js 复制代码
// 要测的函数
function formatPrice(price, currency = '¥') {
  return `${currency}${price.toFixed(2)}`;
}

// 测试
test('格式化价格', () => {
  expect(formatPrice(10.5)).toBe('¥10.50');
  expect(formatPrice(10.5, '$')).toBe('$10.50');
});

黄金法则:如果重构代码不破坏测试,说明你测的是行为,不是实现。

三、组件测试:测交互,不测样式

组件测试(React Testing Library / Vue Test Utils)的目标:模拟用户行为,检查组件渲染和交互是否正确。不关心DOM结构细节,只关心用户能看到什么、能做什么。

适合测的

  • 根据props渲染正确的内容。
  • 点击按钮触发正确回调。
  • 表单输入后数据变化。
  • 异步加载显示loading状态。

不适合测的

  • CSS样式(那是视觉回归测试的事,交给视觉测试工具)。
  • 内部state的具体值(优先测渲染结果)。
  • 第三方UI库的行为(假设它没问题)。

工具:React Testing Library + Jest(官方推荐),Vue Test Utils + Vitest。

例子(React Testing Library):

jsx 复制代码
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';

test('点击按钮增加计数', () => {
  render(<Counter />);
  const button = screen.getByText('增加');
  fireEvent.click(button);
  expect(screen.getByText('计数: 1')).toBeInTheDocument();
});

原则:测用户能看到的东西,不要测内部实现。

四、E2E测试:测关键用户旅程,不测所有交互

E2E测试模拟真实浏览器,跑完整的用户流程。它最像真实用户,但也最慢、最脆弱(网络波动、页面改动容易挂)。

适合测的(3-5个核心流程):

  • 登录 → 访问个人主页 → 修改头像。
  • 搜索商品 → 加入购物车 → 结算 → 支付成功。
  • 未登录访问受保护页面 → 跳转到登录页。

不适合测的

  • 每个细节(比如每个按钮的悬浮效果)。
  • 容易变化的面包屑导航。
  • 第三方依赖的页面。

工具:Cypress(最友好)、Playwright(更可靠)、Puppeteer(较底层)。

例子(Cypress):

js 复制代码
describe('用户登录', () => {
  it('输入正确账号密码后跳转到首页', () => {
    cy.visit('/login');
    cy.get('[data-cy=username]').type('user@example.com');
    cy.get('[data-cy=password]').type('password123');
    cy.get('[data-cy=submit]').click();
    cy.url().should('include', '/dashboard');
    cy.contains('欢迎回来', { timeout: 10000 });
  });
});

维护技巧 :给关键元素加上data-cy属性,避免改样式或文本时测试挂掉。

五、测试覆盖率的谎言

很多团队追求100%覆盖率,结果工程师花大量时间测无关紧要的代码(比如测Redux的action creator是个纯对象)。覆盖率工具(Istanbul)只能告诉你"哪些代码没执行过",不能告诉你"没测到的重要逻辑"。有时100%覆盖率,却漏掉了一个关键的空值判断。

正确的覆盖率指标

  • 核心业务逻辑达到80%以上就行。
  • UI组件覆盖率参考即可,不必强求。
  • 关注未覆盖的重要代码,而不是数字。

六、组合策略:一个电商网站的例子

  • 单元测试:计算折扣、格式化价格、校验表单规则。Jest跑得快,每次提交都跑。
  • 组件测试:商品卡片(渲染正确信息)、购物车弹窗(增加/删除商品)、地址表单(提交按钮禁用直到填写完整)。
  • E2E测试:1. 用户搜索"手机" → 2. 添加第一个商品到购物车 → 3. 登录 → 4. 结算 → 5. 确认订单。就这一个核心流程,保证不崩。

日常开发:单元测试 + 组件测试在CI里跑(每次push)。E2E测试单独流水线,部署前跑一次(因为慢)。

七、测试不是银弹,别为了写而写

  • 重构旧代码,没测试?先别补。补一个挂一个,浪费时间。优先补新功能。
  • 一个bug反复出现,才需要补测试
  • UI改得频繁的区域,不写E2E,写组件测试更稳。

测试是手段,不是目的。目的是信心:当你改完代码,测试全绿,你能放心上线。

八、总结:测试就像买保险

  • 单元测试:车险,便宜,必须买。
  • 组件测试:医疗险,中等,按需买。
  • E2E测试:地震险,贵,只买最关键的。

别买一大堆没用的险,也别裸奔。

相关推荐
去伪存真1 小时前
我自己写的第一个skills--project-core-standards
前端·agent
Data_Journal1 小时前
如何使用cURL更改User Agent
大数据·服务器·前端·javascript·数据库
掌心向暖RPA自动化1 小时前
如何获取网页某个元素在屏幕可见部分的中心坐标影刀RPA懒加载坐标定位技巧
java·javascript·自动化·rpa·影刀rpa
竹林8182 小时前
wagmi v2 多链钱包切换:一个 Uniswap 仿盘项目让我踩了三天坑
前端·javascript
donecoding2 小时前
Playwright MCP 页面捕获:Snapshot、截图、HTML 到底选哪个?
前端·ai编程·前端工程化
你也向往长安城吗2 小时前
最快的 JavaScript navmesh pathfinding3d 算法。
javascript
滕青山2 小时前
在线PDF拆分工具核心JS实现
前端·javascript·vue.js
Smilezyl2 小时前
一个独立开发者,靠一份 markdown 驱动 Claude Code, 用 20 天跑通 9 个包的 monorepo 工程
前端·人工智能·github
技术崽崽2 小时前
不止有 Agent:Cursor 进阶使用技巧全解析
前端