mcpField
F
frontend-testing-best-practices

frontend-testing-best-practices

github.com/sergiodxa测试与质量922.2k 次安装38 周前更新
代码仓库

关于 frontend-testing-best-practices

Testing Best Practices 是一组面向前端项目的测试指南,适用于编写、评审和重构测试的开发者。它强调优先端到端测试、减少模拟,并验证用户可观察的行为,而非组件内部实现。

主要功能

  1. 01优先采用端到端测试
  2. 02仅为纯函数编写单元测试
  3. 03减少复杂模拟
  4. 04通过用户行为验证功能
  5. 05使用可访问的测试选择器
  6. 06将端到端测试放在指定目录

适用场景

  1. 01决定新功能应采用哪类测试
  2. 02为用户流程编写端到端测试
  3. 03评审 React 测试代码
  4. 04重构过度依赖模拟的测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/sergiodxa/agent-skills --skill frontend-testing-best-practices

安装后在 Claude Code 中输入/frontend-testing-best-practices确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

rules/
avoid-component-tests.md
e2e-selectors.md
e2e-test-structure.md
minimize-mocking.md
prefer-e2e-tests.md
unit-test-structure.md

详细介绍

测试最佳实践

编写有效、易维护且能真正增强信心的测试指南。包含 6 条规则,重点是优先编写 E2E 测试、尽量减少模拟,以及测试行为而非实现。

核心理念

  1. 优先编写 E2E 测试,而非单元测试 - 测试整个系统,而不是孤立的部分
  2. 尽量减少模拟 - 如果需要复杂的模拟,请改写 E2E 测试
  3. 测试行为,而非实现 - 测试用户能看到和执行的操作
  4. 避免直接测试 React 组件 - 通过 E2E 测试组件

适用场景

在以下情况参考这些指南:

  • 决定编写哪种类型的测试
  • 编写新的 E2E 或单元测试
  • 审查测试代码
  • 重构测试

规则概要

测试策略(关键)
prefer-e2e-tests - @rules/prefer-e2e-tests.md

默认编写 E2E 测试。仅为纯函数编写单元测试。

// E2E test (PREFERRED) - tests real user flow
test("user can place an order", async ({ page }) => {
  await createTestingAccount(page, { account_status: "active" });
  await page.goto("/catalog");
  await page.getByRole("heading", { name: "Example Item" }).click();
  await page.getByRole("link", { name: "Buy" }).click();
  // ... complete flow
  await expect(page.getByAltText("Thank you")).toBeVisible();
});

// Unit test - ONLY for pure functions
test("formatCurrency formats with two decimals", () => {
  expect(formatCurrency(1234.5)).toBe("$1,234.50");
});
avoid-component-tests - @rules/avoid-component-tests.md

不要对 React 组件进行单元测试。通过 E2E 测试组件,或者完全不测试。

// BAD: Component unit test
describe("OrderCard", () => {
  test("renders amount", () => {
    render(<OrderCard amount={100} />);
    expect(screen.getByText("$100")).toBeInTheDocument();
  });
});

// GOOD: E2E test covers the component naturally
test("order history shows orders", async ({ page }) => {
  await page.goto("/orders");
  await expect(page.getByText("$100")).toBeVisible();
});
minimize-mocking - @rules/minimize-mocking.md

保持模拟简单。如果需要 3 个或更多模拟,请改写 E2E 测试。

// BAD: Too many mocks = write E2E test
vi.mock("~/lib/auth");
vi.mock("~/lib/transactions");
vi.mock("~/hooks/useAccount");

// GOOD: Simple MSW mock for loader test
mockServer.use(
  http.get("/api/user", () => HttpResponse.json({ name: "John" })),
);
E2E 测试(高优先级)
e2e-test-structure - @rules/e2e-test-structure.md

E2E 测试放在 e2e/tests/,而不是 frontend/。

// e2e/tests/order.spec.ts
import { test, expect } from "@playwright/test";
import { addAccountBalance, createTestingAccount } from "./utils";

test.describe("Orders", () => {
  test.beforeEach(async ({ page, context }) => {
    await createTestingAccount(page, { account_status: "active" });
    let cookies = await context.cookies();
    let account_id = cookies.find((c) => c.name === "account_id").value;
    await addAccountBalance({ account_id, amount: 10000, replaceBalance: true });
  });

  test("place order with default values", async ({ page }) => {
    await page.goto("/catalog");
    // ... user flow
  });
});
e2e-selectors - @rules/e2e-selectors.md

使用无障碍选择器:role > label > text > testid。

// GOOD: Role-based (preferred)
await page.getByRole("button", { name: "Submit" }).click();
await page.getByRole("heading", { name: "Dashboard" });

// GOOD: Label-based
await page.getByLabel("Email").fill("[email protected]");

// OK: Test ID when no accessible selector exists
await expect(page.getByTestId("balance")).toHaveText("$1,234");

// BAD: CSS selectors
await page.locator(".btn-primary").click();
单元测试(中优先级)
unit-test-structure - @rules/unit-test-structure.md

单元测试仅用于纯函数。与源文件放在一起。

// app/utils/format.test.ts
import { describe, test, expect } from "vitest";
import { formatCurrency } from "./format";

describe("formatCurrency", () => {
  test("formats positive amounts", () => {
    expect(formatCurrency(1234.5)).toBe("$1,234.50");
  });

  test("handles zero", () => {
    expect(formatCurrency(0)).toBe("$0.00");
  });
});

关键文件

  • e2e/tests/ - E2E 测试(Playwright)
  • e2e/tests/utils.ts - E2E 测试工具
  • vitest.config.ts - 单元测试配置
  • vitest.setup.ts - 使用 MSW 的全局测试设置
  • app/utils/test-utils.ts - 单元测试工具

评论

支持 Markdown