Files
zhuangang/topics/frontend/fe-testing-perf/01-testing-pyramid.md
T

81 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: frontend.fe-testing-perf.testing
title: 前端测试:Vitest 单元测试、Testing Library 组件测试、Playwright 端到端测试
type: lesson
domain: frontend
stack: [fe-testing-perf]
tags: [Vitest, Testing Library, Playwright, 假定时器, Mock, 竞态, 测试金字塔]
level: intermediate
prerequisites: [frontend.react.hooks-rendering]
est_hours: 2.5
related: [frontend.fe-testing-perf.web-vitals, frontend.fe-testing-perf.interview, ai.ai-coding.verify-evidence]
week: [7]
order: 1
---
# 前端测试:Vitest 单元测试、Testing Library 组件测试、Playwright 端到端测试
> BOSS直聘 2026-10-23 的调研中,多个岗位要求“用 AI 写代码并保证质量”:测试是证明 AI 生成代码正确的最直接证据(见 [AI 编程:验证与证据](../../ai/ai-coding/02-verify-evidence.md))。工具版本以官方文档为准:https://vitest.dev/ 、https://testing-library.com/ 、https://playwright.dev/ 。
## 解释说明
### 测试金字塔
| 层级 | 工具 | 测什么 | 数量 |
| --- | --- | --- | --- |
| 单元测试 | Vitest | 纯函数、状态机、工具函数、组合式函数 / Hooks | 最多 |
| 组件测试 | Vitest + Testing Library(jsdom) | 组件在用户操作下的表现 | 适中 |
| 端到端测试 | Playwright | 真实浏览器中的关键业务流程 | 少而精 |
### Testing Library 的原则
**按用户的方式测试**:通过角色、标签文字查找元素(`getByRole`、`getByLabelText`),而不是 CSS 类名或组件内部状态;断言用户能看到的结果。好处是重构实现时测试不用改,同时促使组件具备可访问性。
### 常用技巧
- **假定时器**:`vi.useFakeTimers()` + `vi.advanceTimersByTimeAsync()` 测试防抖、轮询、超时,不需要真的等待。
- **Mock**:`vi.fn()` 模拟依赖(接口请求函数作为参数注入,测试时传入假的实现);端到端测试中用 `page.route` 拦截接口返回固定数据。
- **测竞态**:让旧请求比新请求慢,断言最终显示新结果。
- **端到端测试稳定性**:使用自动等待的断言(`await expect(locator).toHaveCount(2)`),不要写固定的 `sleep`。
## 案例
订单搜索框:防抖 300ms;输入变化时中止旧请求;失败显示错误。三个测试分别覆盖防抖、竞态和失败;把组件中的 `controller.abort()` 删除后,竞态测试会失败——证明测试真的在保护这个行为。
## 代码示例
完整示例:[examples/search-box](examples/search-box/)(`vitest run`:3 个测试通过;`tsc` 通过;删除中止逻辑后竞态测试失败,已验证)。
```tsx
it("竞态:慢的旧请求晚返回,不会覆盖新结果", async () => {
const search: SearchFn = (q, signal) =>
new Promise((resolve) => {
const ms = q === "old" ? 1000 : 100;
const t = setTimeout(() => resolve([`结果-${q}`]), ms);
signal.addEventListener("abort", () => clearTimeout(t));
});
render(<SearchBox search={search} />);
typeInto("old");
await act(() => vi.advanceTimersByTimeAsync(300));
typeInto("new");
await act(() => vi.advanceTimersByTimeAsync(2000));
expect(screen.getByRole("list", { name: "搜索结果" }).textContent).toBe("结果-new");
});
```
Playwright 端到端测试(示意,未在浏览器中实际运行):[e2e/search.spec.ts](examples/search-box/e2e/search.spec.ts)。
## 易错点
- 测试实现细节(组件内部 state、私有方法),重构就挂。
- 用真实定时器等待,测试又慢又不稳定。
- 组件内部直接 `fetch`,测试只能 mock 全局对象:把依赖作为参数或通过上下文注入更好测。
- 端到端测试依赖测试环境的真实数据,数据一变就失败。
- 只追求覆盖率数字:没有断言的测试也能提高覆盖率。
## 面试常问
- 你们项目怎么做前端测试?测试金字塔每层测什么?
- 怎么测试防抖、定时器、异步请求?
- 端到端测试不稳定怎么办?