关于 frontend-testing-best-practices
Testing Best Practices 是一组面向前端项目的测试指南,适用于编写、评审和重构测试的开发者。它强调优先端到端测试、减少模拟,并验证用户可观察的行为,而非组件内部实现。
主要功能
- 01优先采用端到端测试
- 02仅为纯函数编写单元测试
- 03减少复杂模拟
- 04通过用户行为验证功能
- 05使用可访问的测试选择器
- 06将端到端测试放在指定目录
适用场景
- 01决定新功能应采用哪类测试
- 02为用户流程编写端到端测试
- 03评审 React 测试代码
- 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.mde2e-selectors.mde2e-test-structure.mdminimize-mocking.mdprefer-e2e-tests.mdunit-test-structure.md详细介绍
测试最佳实践
编写有效、易维护且能真正增强信心的测试指南。包含 6 条规则,重点是优先编写 E2E 测试、尽量减少模拟,以及测试行为而非实现。
核心理念
- 优先编写 E2E 测试,而非单元测试 - 测试整个系统,而不是孤立的部分
- 尽量减少模拟 - 如果需要复杂的模拟,请改写 E2E 测试
- 测试行为,而非实现 - 测试用户能看到和执行的操作
- 避免直接测试 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