知识广场

按学科筛选:计算机科学 / 前端开发 / 测试
清除筛选

«计算机科学 / 前端开发 / 测试» 分类下共 2 篇帖子

Vitest vs Jest:2026 看 JS 测试框架

## 起因 JS 项目测试框架历史: - mocha + chai + sinon(老) - jest(Facebook,2014+,事实标准) - vitest(Vite 团队,2021+,Vite-native) 新项目选谁?老 jest 项目要不要迁?下面经验。 ## jest ```js // my.test.js const { add } = require('./math'); describe('math', () => { test('add', () => { expect(add(1, 2)).toBe(3); }); }); ``` ```bash npx jest ``` 10 年生态,全 JS 圈子默认。 ### 优势 - 教程 / 答案最多 - 巨大插件生态(jest-dom / jest-axe / ...) - snapshot testing 成熟 - mock 系统强(auto-mock / manual mock) ### 劣势 - 启动慢(2-5 秒只为跑 1 个测试) - ESM 支持差(CJS 优先,ESM 配置复杂) - TS 要 ts-jest / @swc/jest 等 - 慢(百 test 几十秒) ## vitest ```js // my.test.js import { test, expect, describe } from 'vitest'; import { add } from './math'; describe('math', () => { test('add', () => { expect(add(1, 2)).toBe(3); }); }); ``` ```bash npx vitest ``` API jest-compat(`expect` / `describe` 大部分一致)。 基于 Vite,原生 ESM + esbuild + TS 直接。 ### 优势 - **极快**:watch mode HMR-like,改文件即重跑相关 test - ESM 原生 + TS 原生 - Vite 项目同 config 共享 - jest API 兼容(容易迁移) - 内置 coverage / UI / browser mode ### 劣势 - 生态比 jest 小(但每年增长快) - 某些 jest 插件没等价 - snapshot 跟 jest 略不同(导致迁移微调) ## 性能对比 中型项目 500 test: | | jest | vitest | |---|---|---| | cold start | 8s | 1.5s | | 全跑 | 25s | 6s | | watch(改 1 file) | 5s | 0.3s | | coverage | 35s | 10s | vitest 普遍 3-5x 快。开发循环 watch mode 差距更大。 ## 写法对比 写法 99% 一致: ```js // 通用 describe('x', () => { beforeEach(() => { ... }); test('does y', async () => { expect(...).toBe(...); }); }); ``` vitest 加: ```js import { vi } from 'vitest'; // jest 是全局 vi → jest.fn / mock ``` `jest.fn()` → `vi.fn()`,`jest.spyOn` → `vi.spyOn`,`jest.mock` → `vi.mock`。 config 注入全局可让 jest 写法直接跑: ```ts // vitest.config.ts export default defineConfig({ test: { globals: true }, // 启用 describe / test / expect 全局 }); ``` ## mock ```js // vitest import { vi } from 'vitest'; vi.mock('./api', () => ({ fetchUser: vi.fn(() => Promise.resolve({ name: 'mock' })), })); const spy = vi.spyOn(console, 'log'); ``` 跟 jest 几乎一样。 ## snapshot ```js expect(rendered).toMatchSnapshot(); ``` 生成 `__snapshots__/my.test.js.snap`。 vitest 用 jest 相同格式。 inline snapshot: ```js expect(rendered).toMatchInlineSnapshot(`"<div>hello</div>"`); ``` review 时 inline 直观。 ## React Testing Library 集成 ```ts // vitest.config.ts import { defineConfig } from 'vitest/config'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], test: { environment: 'jsdom', setupFiles: './test/setup.ts', }, }); ``` ```ts // test/setup.ts import '@testing-library/jest-dom/vitest'; // matchers ``` ```tsx import { render, screen } from '@testing-library/react'; import { test, expect } from 'vitest'; import App from './App'; test('renders title', () => { render(<App />); expect(screen.getByText('Hello')).toBeInTheDocument(); }); ``` 跟 jest 配 RTL 几乎一样。 ## browser mode(vitest 1.0+) ```ts test: { browser: { enabled: true, name: 'chromium' }, } ``` 测试在真实浏览器跑(替代 jsdom)。 适合:测 component 在真实环境(layout / CSS / fetch)。 jest 不能直接跑浏览器(要 jest-playwright 等组合)。 ## 迁移 jest → vitest ```bash npm install -D vitest @vitest/ui ``` `package.json`: ```json "scripts": { "test": "vitest", "test:ui": "vitest --ui", "test:coverage": "vitest --coverage" } ``` 代码层 99% 测试直接跑: ```ts // 全局 import 替代 - import { describe, test, expect } from '@jest/globals'; + // (with globals: true 不需要) + import { describe, test, expect } from 'vitest'; - jest.fn() + vi.fn() - jest.mock('./api') + vi.mock('./api') ``` 一般 sed 批量替换 + 个别手动调。 中型项目几小时迁移完。 ## CI 集成 ```yaml - run: pnpm test --coverage - uses: codecov/codecov-action@v4 ``` 不变。GitHub Actions / GitLab / CircleCI 都 vitest 友好。 ## 真实迁移 case 我们一个 React 项目,jest + RTL + 200 test: - ci 时间从 90s → 25s - watch mode 改文件秒返馈 - ts-jest 配置删了(vite 自带 TS) - 配置文件简化 但迁完一周才完全稳: - 几个 mock 行为微差 - snapshot whitespace 略不同需 regenerate - 某些 jest plugin(如 jest-axe)没 vitest 版本(找替代) ## 何时不必迁 - 老项目大 jest,团队稳定 → 不动 - 用极偏 jest 插件 → 维持 ## 决策 - **新项目** → vitest(无脑选) - **大老项目 + 团队稳** → jest,可不迁 - **小老项目** → 半天迁 ## 与 node:test 对比 Node 20+ 内置 `node --test`: ```js import { test } from 'node:test'; import assert from 'node:assert'; test('add', () => { assert.strictEqual(add(1, 2), 3); }); ``` 无依赖。简单 unit test 够。 但 mock / snapshot / coverage 弱于 jest / vitest,复杂项目仍 vitest。 ## 踩过的坑 1. **vi.mock 提升**:vite 把 mock 提升到 file 顶部 → 跟 import 顺序 交互奇怪。简单场景 OK,复杂用 mock factory + lazy。 2. **`globals: true` 没设**:jest 写法报 `describe is not defined`。 3. **CSS import 报错**:vitest 不像 jest 自动 mock CSS。配 `vitest.config.ts` 加 `css: true` 或 `css.modules.classNameStrategy`。 4. **timeout 默认 5s**:复杂 e2e 测试超时。`testTimeout: 30000`。 5. **watch 没 trigger**:file change 但 test 没重跑 → vitest cache bug。`vitest --no-cache` 或 restart。

Playwright vs Cypress:E2E 测试选型

## 起因 E2E 测试两个主流: - **Cypress**:先发优势,DX 好,2017+ - **Playwright**(Microsoft,2020+):更快 / 更全 / 现代 新项目都选 Playwright,下面对比。 ## 写法 ```ts // Playwright import { test, expect } from '@playwright/test'; test('login', async ({ page }) => { await page.goto('/login'); await page.fill('[name=email]', '[email protected]'); await page.fill('[name=password]', 'pass'); await page.click('button[type=submit]'); await expect(page.locator('h1')).toHaveText('Dashboard'); }); ``` ```js // Cypress describe('login', () => { it('logs in', () => { cy.visit('/login'); cy.get('[name=email]').type('[email protected]'); cy.get('[name=password]').type('pass'); cy.get('button[type=submit]').click(); cy.contains('h1', 'Dashboard'); }); }); ``` API 风格差异: - Playwright:async/await 标准 JS - Cypress:chained command,自定义 promise-like Cypress chain 直观但 debug 复杂。Playwright async 更标准。 ## 浏览器支持 | | Playwright | Cypress | |---|---|---| | Chromium | ✅ | ✅ | | Firefox | ✅ | ✅ | | WebKit (Safari) | ✅ | ❌(实验) | | Mobile emulation | ✅ | 部分 | | 并发跨浏览器 | ✅ | 部分 | Playwright **WebKit 支持** 是杀手:Safari bug 能在 CI 测出(不能跑 真 Safari 但 WebKit engine 同一个)。 ## 性能 我们一个项目 200 E2E 测试: | | Cypress | Playwright | |---|---|---| | 全跑 | 12 min | 4 min | | parallel (4 worker) | 5 min | 1.5 min | | 启动 (first test) | 8s | 2s | Playwright 3x 快主要因为: - 真正并行(多 worker,单 process 多 context) - WebSocket 协议直连(vs Cypress iframe-based) - 默认 headless 优化 Cypress 并行要付费云服务 (Cypress Cloud) 或者多 CI runner。 Playwright 单进程并行免费。 ## auto-wait ```ts // Playwright await page.click('button'); // 自动等元素可点 await expect(page.locator('h1')).toHaveText('done'); // 自动 retry until match ``` ```js // Cypress cy.get('button').click(); // 自动 retry cy.contains('h1', 'done'); // 自动 retry ``` 两者都有 auto-wait,行为相似。 Playwright 的 `expect().toBeVisible()` 等 matcher 配合 retry 内置。 ## test isolation Cypress:每 test 在新 iframe 跑(同 browser)。 Playwright:每 test 在新 BrowserContext(独立 cookie / storage)。 Playwright context 隔离更彻底,并行更安全。 ## fixture / setup ```ts // Playwright test.beforeEach(async ({ page }) => { await page.goto('/login'); }); // 共享 page state (storageState) test.use({ storageState: 'auth.json' }); ``` ```js // Cypress beforeEach(() => cy.visit('/login')); // 共享 login Cypress.Commands.add('login', () => { ... }); cy.login(); ``` Playwright `storageState` 让"登录一次" 然后多 test 复用 session → 速度 +30%。Cypress 类似 `cy.session()`。 ## debug Playwright: - VS Code 扩展:UI mode 一键 step through - trace viewer:自动录制每步 DOM + network + screenshot - `--debug` 启动 Inspector ```bash npx playwright test --ui # 交互 UI npx playwright show-trace ... # 看失败 trace ``` Cypress: - GUI mode 一直是核心(time-travel debugger) - 录像 / 截图 两者 debug 体验都好,Playwright trace viewer 后来居上更详细。 ## codegen ```bash npx playwright codegen example.com ``` 打开浏览器 → 你点 / 输 → Playwright 自动生成代码。 新人写第一个测试快。 Cypress 也有 Studio 但不如 Playwright codegen 成熟。 ## visual regression Playwright 内置 snapshot: ```ts await expect(page).toHaveScreenshot('home.png'); ``` Cypress 要装插件(cypress-image-snapshot)。 ## API testing Playwright 也能测 API: ```ts const response = await request.post('/api/users', { data: {...} }); expect(response.status()).toBe(201); ``` Cypress 也有 `cy.request()`。 两者都行,API 测试不是主战场。 ## component testing Cypress component testing 较早(2021+)。 Playwright 1.30+ 也有 component testing(experimental)。 实际:我们用 vitest + Testing Library 做 component test, Playwright 只跑 full E2E。 ## CI 集成 ```yaml # Playwright GitHub Actions - run: npx playwright install --with-deps - run: npx playwright test - uses: actions/upload-artifact@v4 if: failure() with: name: playwright-report path: playwright-report/ ``` 失败自动上传 HTML report + trace + screenshot → review 方便。 Cypress: ```yaml - run: npx cypress run ``` 跟 Cypress Cloud 集成更好(recorded video / dashboard),但要付费。 ## flake 处理 E2E 测试 flake(不稳定)是普遍问题。 Playwright retries: ```ts // playwright.config.ts export default { retries: process.env.CI ? 2 : 0, }; ``` Cypress 也支持 retry。 flake 治本: - locator 用 stable selector(test-id 而不是 CSS class) - 等明确条件(不是 sleep) - isolate test(不依赖前一 test state) ## locator best practice ```ts // bad: brittle page.locator('.btn-primary:nth-child(3)'); // good page.getByRole('button', { name: 'Submit' }); page.getByTestId('submit-button'); ``` Playwright `getByRole` / `getByText` / `getByLabel` 跟 RTL 一致。 accessibility-friendly + 稳定。 ## 选择决策 - **新项目** → Playwright(性能 + 跨浏览器 + 免费并行) - **老 Cypress 项目 + 测试稳** → 不必迁 - **macOS Safari 是 target** → Playwright(WebKit) - **团队偏好 GUI debug** → Cypress 仍胜 我新项目 100% Playwright。 ## 真实迁移 case 某客户项目 Cypress 200 测试,CI 跑 15 分钟,flaky。 迁 Playwright: - 1 周转换 + 调优 - CI 时间 → 4 分钟(4 worker parallel) - flake rate 从 5% → < 1% - 没付 Cypress Cloud 钱(每月 $75) 主要工作: - API 对应(cy.get → page.locator) - custom command → fixture / helper - 调整等待条件(用 expect with auto-retry) ## 踩过的坑 1. **CI 没装 browser dependencies**:`npx playwright install` 也要 `--with-deps`(Linux 系统依赖)。 2. **fixture 滥用**:把太多 setup 塞 fixture → test 慢。balance。 3. **`waitForLoadState('networkidle')`**:永远不到 idle(持续 polling) → 超时。改 `domcontentloaded` 或 specific selector。 4. **trace 文件大**:每 test 一个 zip 几 MB → CI 100 test 100 MB artifact。只 retain failure trace(`trace: 'retain-on-failure'`)。 5. **headed mode 与 headless 行为差**:极少数 case 一致性问题(如 focus / window size)。CI 主要 headless,dev 偶尔 headed debug。