前端测试的价值与金字塔

前端代码改起来慌,多半是没有测试兜底:改一个组件,不知道哪里会跟着崩;重构一次,全靠人工点一遍页面。测试把"出问题"提前到提交前,让改代码有了安全网。测试金字塔告诉我们,越底层的测试越便宜越密集:单元测试最多,覆盖纯函数与组件逻辑;集成测试其次,验证模块协作;端到端最少但最接近真实用户。

前端测试的难点在于环境复杂:浏览器 API、异步、网络、组件交互都要模拟。好在现代工具链已经把门槛降到很低:Vitest 管单元与组件测试,Playwright 管端到端,两者配合覆盖绝大部分场景。

本文核心知识点
  • 单元测试验证最小逻辑单元,快而密。
  • 组件测试模拟交互,验证渲染行为。
  • 端到端真实浏览器跑关键用户路径。
  • Mock隔离外部依赖,测试才稳定。
前端测试配图:蓝紫渐变背景一条蓝色传送带载着深浅方块经过布满白色圆点的蓝色闸门面板,末端堆叠纯色终点方块,扁平矢量插画
代码方块经过检查闸门流向终点,正是自动化测试流水线的意象。

Vitest 单元测试入门

Vitest 与 Vite 同源,配置几乎为零,速度还快。先给纯函数写测试:格式化、校验、计算这类无副作用的逻辑,是最值得测也最好测的部分。测试三件套是描述、断言与期望:describe 分组,it 定义用例,expect 断言结果。

import { describe, it, expect } from "vitest";
import { calcTotal } from "./cart";

describe("calcTotal", () => {
  it("计算总价并应用折扣", () => {
    expect(calcTotal([{ price: 100, qty: 2 }], 0.9))
      .toBe(180);
  });
});

边界情况是单元测试的价值所在:空数组、负数、超大数、非法输入,每个都要有断言。测试不是写给代码看的,是写给下一个改代码的人看的,用例名字要能读出业务意图。

组件与交互测试

组件测试用 Testing Library 的思路:不测内部实现,只从用户视角看渲染结果与交互行为。挂载组件、触发点击、断言界面上出现了什么。查询优先用角色与文本,而不是 DOM 类名,这样测试更像用户,也更耐重构。

import { render, screen, fireEvent } from "@testing-library/react";
import Counter from "./Counter";

it("点击按钮计数加一", () => {
  render(<Counter />);
  fireEvent.click(screen.getByRole("button", { name: "加一" }));
  expect(screen.getByText("1")).toBeTruthy();
});

交互测试要覆盖关键路径与异常态:表单提交成功与失败、加载中状态、空数据展示。异步更新用 waitFor 等待,避免断言跑在渲染之前。组件测试的速度介于单元与端到端之间,是性价比最高的中间层。

Playwright 端到端测试

单元与组件测试都过不了浏览器这一关:真实跳转、真实网络、真实布局。Playwright 用真实浏览器驱动,自动等待元素就绪,写起来像在描述用户操作。端到端测试不用多,覆盖最核心的用户路径就够:注册登录、下单支付、列表加载。

import { test, expect } from "@playwright/test";

test("用户能搜索到文章", async ({ page }) => {
  await page.goto("/");
  await page.fill("#search-input", "Kafka");
  await page.click("button[type=submit]");
  await expect(page.locator(".result-item")).toHaveCount(1);
});

端到端测试要克制数量,每条路径都慢且脆。测试环境要独立、数据可重置,避免测试互相污染。Playwright 还能录屏与截图,失败时留下的证据能大幅加速排障。

测试数据与 Mock 策略

测试不能依赖真实后端:接口未就绪、数据随机、网络抖动都会让测试不可复现。主流做法是 Mock 网络层:单元与组件测试用 Mock Service Worker 拦截请求返回固定数据;端到端测试可以用测试账号连测试环境,也可以用路由拦截注入假数据。

// MSW 拦截接口返回固定数据
rest.get("/api/orders", (req, res, ctx) => {
  return res(ctx.json([{ id: 1, total: 199 }]));
});
Mock 三层
  • 函数级单测 mock 模块依赖。
  • 网络级MSW 拦截接口响应。
  • 环境级测试库与种子数据。

Mock 要克制:只隔离不稳定外部依赖,业务逻辑本身要真实运行。测试数据用工厂函数生成,随机但有种子,既覆盖多样场景又能复现失败用例。