全部文章

前端测试指南

Vitest 单元测试、Playwright E2E 测试、测试策略与最佳实践

目录 20 节

前端测试指南

这页适合作为“前端项目测试策略总览”。重点不是把测试写得越多越好,而是把单元测试、组件测试、E2E 测试分别放在最有价值的位置,避免测试维护成本反过来拖慢迭代。

推荐建立顺序

比较稳妥的测试落地方式通常是:

  1. 先补工具函数与关键业务逻辑的单元测试
  2. 再为高复用组件补组件测试
  3. 最后只给关键用户路径加少量 E2E 测试

这样做能更快见效,也更不容易因为 E2E 过多导致 CI 变慢、失败难排查。

Vitest

Vitest 是 Vite 生态的测试框架,兼容 Jest API。

安装

pnpm add -D vitest

基础用法

// sum.ts
export function sum(a: number, b: number) {
  return a + b;
}

// sum.test.ts
import { describe, it, expect } from "vitest";
import { sum } from "./sum";

describe("sum", () => {
  it("adds two numbers", () => {
    expect(sum(1, 2)).toBe(3);
  });

  it("handles negative numbers", () => {
    expect(sum(-1, 1)).toBe(0);
  });
});
vitest --run              # 单次运行
vitest --coverage         # 覆盖率

常用断言

expect(value).toBe(42); // 严格相等
expect(value).toEqual({ a: 1 }); // 深度相等
expect(value).toBeTruthy(); // 真值
expect(value).toBeNull(); // null
expect(value).toContain("hello"); // 包含
expect(value).toHaveLength(3); // 长度
expect(fn).toThrow("error"); // 抛出错误
expect(fn).toHaveBeenCalledWith("arg"); // 调用参数

Mock

import { vi } from "vitest";

// 函数 mock
const fn = vi.fn();
fn("hello");
expect(fn).toHaveBeenCalledWith("hello");

// 模块 mock
vi.mock("./api", () => ({
  fetchUser: vi.fn().mockResolvedValue({ name: "Domi" }),
}));

// 定时器 mock
vi.useFakeTimers();
setTimeout(() => {}, 1000);
vi.advanceTimersByTime(1000);
vi.useRealTimers();

Vue 组件测试

pnpm add -D @vue/test-utils happy-dom
// vitest.config.ts
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    environment: "happy-dom",
  },
});
import { mount } from "@vue/test-utils";
import MyComponent from "./MyComponent.vue";

it("renders message", () => {
  const wrapper = mount(MyComponent, {
    props: { msg: "Hello" },
  });
  expect(wrapper.text()).toContain("Hello");
});

it("emits event on click", async () => {
  const wrapper = mount(MyComponent);
  await wrapper.find("button").trigger("click");
  expect(wrapper.emitted("submit")).toBeTruthy();
});

Playwright

Playwright 用于端到端(E2E)测试。

安装

pnpm add -D @playwright/test
npx playwright install

基础测试

// tests/home.spec.ts
import { test, expect } from "@playwright/test";

test("homepage loads", async ({ page }) => {
  await page.goto("/");
  await expect(page).toHaveTitle(/DomiVault/);
  await expect(page.locator("h1")).toBeVisible();
});

test("search works", async ({ page }) => {
  await page.goto("/");
  await page.keyboard.press("Control+k");
  await page.fill('input[placeholder*="搜索"]', "docker");
  await expect(page.locator(".search-result-item")).toHaveCount(1);
});

test("navigation", async ({ page }) => {
  await page.goto("/");
  await page.click("text=文章");
  await expect(page).toHaveURL("/docs");
});

配置

// playwright.config.ts
import { defineConfig } from "@playwright/test";

export default defineConfig({
  testDir: "./tests",
  webServer: {
    command: "pnpm dev",
    port: 3000,
    reuseExistingServer: true,
  },
  use: {
    baseURL: "http://localhost:3000",
    screenshot: "only-on-failure",
  },
});

测什么最划算

可以用下面的思路判断:

  • 纯函数 / 数据转换 / 工具方法:优先 Vitest
  • 组件渲染 / props / emits / 交互:优先组件测试
  • 登录、搜索、下单、提交表单这类核心路径:优先 Playwright

相反,下面这些不一定值得重度测试:

  • 纯静态展示且几乎不变的简单组件
  • 依赖实现细节、改样式就会碎掉的脆弱选择器测试
  • 可以被更小粒度测试覆盖的重复 E2E 流程

测试策略

单元测试(Vitest)    → 工具函数、composables、纯逻辑
组件测试(Vue Test Utils) → 组件渲染、交互、事件
E2E 测试(Playwright) → 用户流程、页面导航、集成

经典测试金字塔:大量单元测试 → 适量集成测试 → 少量 E2E 测试。

常见问题

E2E 很不稳定

高频原因通常包括:

  • 依赖脆弱的文案或 CSS 选择器
  • 没等待页面真正稳定就断言
  • 本地、CI、预览环境的数据和网络条件不一致

优先使用稳定的定位方式,例如 role、label、test id,并减少对动画、时间和随机数据的依赖。

Mock 太多,测了等于没测

单元测试里可以 mock,但不要把关键流程全部 mock 掉。否则测试能过,并不代表真实集成链路能过。

覆盖率高,不代表质量高

覆盖率只能说明代码被跑到了,不说明断言真的有价值。比起追 100% 覆盖率,更重要的是覆盖高风险路径、边界条件和错误处理。

CI 建议

  • 轻量单元测试可以在每次 push / PR 都跑
  • E2E 可以放在 PR、主分支或预览环境上跑
  • 失败时保留截图、trace、视频,有助于快速回溯

延伸阅读

参考链接

阅读建议
  • - 先读标题和摘要,再结合目录决定从哪个章节开始精读。
  • - 看到具体命令、配置或步骤时,尽量在自己的环境里同步验证。
  • - 如果你只是快速查资料,可先看目录和相关文档,再决定是否深入全文。
适合谁看
  • - 希望把零散经验整理成长期可复用工作流的人
  • - 想先建立认知,再决定是否深入实践的人
  • - 希望阅读时顺手建立自己的操作清单或收藏体系的人
执行前检查
  • - 先浏览标题、摘要和目录,带着问题阅读会更高效
  • - 顺手记录真正对你有用的命令、链接和注意事项,避免重复搜索
  • - 如果页面里提到相关文档,尽量一起打开对照,效果通常更完整
同类内容
← 上一篇本地 LLM 部署指南