Jest 性能优化 #

性能优化概述 #

测试速度对开发效率至关重要。Jest 提供了多种优化手段来提升测试执行速度。

text
┌─────────────────────────────────────────────────────────────┐
│                    性能优化策略                              │
├─────────────────────────────────────────────────────────────┤
│  1. 并行执行 - 充分利用多核 CPU                              │
│  2. 缓存优化 - 避免重复工作                                  │
│  3. 增量测试 - 只运行相关测试                                │
│  4. 测试隔离 - 减少测试间依赖                                │
│  5. 配置优化 - 减少不必要的处理                              │
└─────────────────────────────────────────────────────────────┘

并行执行 #

maxWorkers 配置 #

javascript
// jest.config.js
module.exports = {
  maxWorkers: '50%',  // 使用 50% CPU 核心
  // 或
  maxWorkers: 4,      // 固定使用 4 个 worker
  // 或
  maxWorkers: 1,      // 串行执行
};

命令行指定 #

bash
# 使用 50% CPU
jest --maxWorkers=50%

# 使用固定数量
jest --maxWorkers=4

# 串行执行
jest --runInBand

CI 环境配置 #

javascript
// jest.config.js
module.exports = {
  maxWorkers: process.env.CI ? 2 : '50%',
};

不同场景的建议 #

场景 建议 maxWorkers
本地开发 50%
CI 环境 2-4
大型项目 4-8
内存受限 1-2

缓存优化 #

启用缓存 #

javascript
// jest.config.js
module.exports = {
  cache: true,
  cacheDirectory: '/tmp/jest_cache',
};

清除缓存 #

bash
# 清除缓存
jest --clearCache

# 忽略缓存运行
jest --no-cache

缓存位置 #

bash
# 查看缓存位置
jest --showConfig | grep cacheDirectory

增量测试 #

只运行修改的测试 #

bash
# 只运行与修改文件相关的测试
jest --onlyChanged

# 只运行与 git 变更相关的测试
jest --changedSince=main

Watch 模式 #

bash
# 监听模式
jest --watch

# 监听所有文件
jest --watchAll

配置 watch 插件 #

javascript
// jest.config.js
module.exports = {
  watchPlugins: [
    'jest-watch-typeahead/filename',
    'jest-watch-typeahead/testname',
  ],
};

测试隔离优化 #

避免共享状态 #

javascript
// ❌ 不好的做法
describe('shared state', () => {
  let counter = 0;

  test('test 1', () => {
    counter++;
    expect(counter).toBe(1);
  });

  test('test 2', () => {
    // 依赖 test 1 的状态
    expect(counter).toBe(1);
  });
});

// ✅ 好的做法
describe('isolated state', () => {
  let counter;

  beforeEach(() => {
    counter = 0;
  });

  test('test 1', () => {
    counter++;
    expect(counter).toBe(1);
  });

  test('test 2', () => {
    expect(counter).toBe(0);
  });
});

减少钩子开销 #

javascript
// ❌ 不好的做法 - 每个测试都执行重操作
beforeEach(async () => {
  await setupDatabase();
  await seedData();
});

// ✅ 好的做法 - 只在需要时执行
describe('database tests', () => {
  beforeAll(async () => {
    await setupDatabase();
  });

  beforeEach(async () => {
    await clearData();
  });
});

转换优化 #

优化 transformIgnorePatterns #

javascript
// jest.config.js
module.exports = {
  transformIgnorePatterns: [
    // 只转换需要的 node_modules
    'node_modules/(?!(react-router-dom|axios)/)',
  ],
};

使用更快的转换器 #

javascript
// 使用 esbuild-jest 替代 babel-jest
module.exports = {
  transform: {
    '^.+\\.(js|jsx|ts|tsx)$': 'esbuild-jest',
  },
};

跳过不必要的转换 #

javascript
module.exports = {
  transform: {
    '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
  },
  transformIgnorePatterns: [
    'node_modules/',
    '\\.css$',
    '\\.scss$',
  ],
};

Mock 优化 #

减少不必要的 Mock #

javascript
// ❌ 不好的做法 - Mock 所有依赖
jest.mock('lodash');
jest.mock('axios');
jest.mock('moment');

// ✅ 好的做法 - 只 Mock 必要的依赖
jest.mock('axios');

使用轻量 Mock #

javascript
// ❌ 不好的做法 - 复杂的 Mock
jest.mock('./api', () => {
  const original = jest.requireActual('./api');
  return {
    ...original,
    fetch: jest.fn(() => {
      return new Promise((resolve) => {
        setTimeout(() => resolve({ data: 'test' }), 100);
      });
    }),
  };
});

// ✅ 好的做法 - 简单的 Mock
jest.mock('./api', () => ({
  fetch: jest.fn().mockResolvedValue({ data: 'test' }),
}));

测试文件优化 #

拆分大测试文件 #

javascript
// ❌ 不好的做法 - 一个大文件
// user.test.js - 1000+ 行

// ✅ 好的做法 - 拆分为多个文件
// user.auth.test.js
// user.profile.test.js
// user.settings.test.js

使用 test.concurrent #

javascript
// 并行运行测试
test.concurrent('concurrent test 1', async () => {
  await someAsyncOperation();
});

test.concurrent('concurrent test 2', async () => {
  await someAsyncOperation();
});

跳过慢测试 #

javascript
// 使用 test.skip 跳过慢测试
test.skip('slow test', () => {
  // 耗时测试
});

// 条件跳过
const runSlowTests = process.env.RUN_SLOW_TESTS === 'true';
const testFn = runSlowTests ? test : test.skip;

testFn('slow test', () => {
  // 耗时测试
});

快照优化 #

减小快照大小 #

javascript
// ❌ 不好的做法 - 整个组件树
expect(container.innerHTML).toMatchSnapshot();

// ✅ 好的做法 - 关键部分
expect(screen.getByRole('button')).toMatchSnapshot();

使用内联快照 #

javascript
// 内联快照更快
expect(value).toMatchInlineSnapshot(`"expected value"`);

配置优化 #

减少测试匹配范围 #

javascript
// jest.config.js
module.exports = {
  testMatch: [
    '<rootDir>/src/**/*.test.{js,ts}',
  ],
  testPathIgnorePatterns: [
    '/node_modules/',
    '/dist/',
    '/coverage/',
  ],
};

减少覆盖率收集 #

javascript
// 只在需要时收集覆盖率
module.exports = {
  collectCoverage: false,  // 默认不收集
};

// CI 中启用
// jest --coverage

优化模块解析 #

javascript
module.exports = {
  moduleDirectories: ['node_modules', 'src'],
  modulePaths: ['<rootDir>/src'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
  },
};

性能分析 #

使用 --detectLeaks #

bash
# 检测内存泄漏
jest --detectLeaks

使用 --logHeapUsage #

bash
# 记录内存使用
jest --logHeapUsage

使用 --detectOpenHandles #

bash
# 检测未关闭的句柄
jest --detectOpenHandles

性能分析脚本 #

javascript
// package.json
{
  "scripts": {
    "test:perf": "node scripts/test-performance.js"
  }
}

// scripts/test-performance.js
const { execSync } = require('child_process');

const start = Date.now();
execSync('jest --runInBand', { stdio: 'inherit' });
const duration = Date.now() - start;

console.log(`Tests completed in ${duration}ms`);

CI 优化 #

分片测试 #

bash
# 将测试分为 4 片,运行第 1 片
jest --shard=1/4

# 在 CI 中并行运行
# Job 1: jest --shard=1/4
# Job 2: jest --shard=2/4
# Job 3: jest --shard=3/4
# Job 4: jest --shard=4/4

GitHub Actions 分片 #

yaml
jobs:
  test:
    strategy:
      matrix:
        shard: [1/4, 2/4, 3/4, 4/4]
    steps:
      - run: npm test -- --shard=${{ matrix.shard }}

缓存依赖 #

yaml
# GitHub Actions
- name: Cache node modules
  uses: actions/cache@v3
  with:
    path: |
      ~/.npm
      node_modules
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

- name: Cache Jest
  uses: actions/cache@v3
  with:
    path: .jest-cache
    key: ${{ runner.os }}-jest-${{ hashFiles('**/package-lock.json') }}

性能优化清单 #

text
□ 并行执行
  ├── 配置合理的 maxWorkers
  ├── CI 环境使用固定 worker 数
  └── 使用 test.concurrent

□ 缓存优化
  ├── 启用 Jest 缓存
  ├── CI 中缓存依赖
  └── 定期清理缓存

□ 增量测试
  ├── 使用 --onlyChanged
  ├── 使用 --changedSince
  └── Watch 模式开发

□ 测试隔离
  ├── 避免共享状态
  ├── 减少钩子开销
  └── 清理 Mock

□ 转换优化
  ├── 优化 transformIgnorePatterns
  ├── 使用更快的转换器
  └── 跳过不必要的转换

□ Mock 优化
  ├── 减少不必要的 Mock
  ├── 使用轻量 Mock
  └── 清理 Mock

□ 配置优化
  ├── 减少测试匹配范围
  ├── 减少覆盖率收集
  └── 优化模块解析

□ CI 优化
  ├── 使用测试分片
  ├── 缓存依赖
  └── 并行运行

性能基准 #

测试执行时间 #

项目规模 建议执行时间
小型项目 < 10s
中型项目 < 30s
大型项目 < 60s

优化前后对比 #

text
优化前:
- 测试数量: 500
- 执行时间: 120s
- 内存使用: 2GB

优化后:
- 测试数量: 500
- 执行时间: 30s (↓75%)
- 内存使用: 800MB (↓60%)

下一步 #

现在你已经掌握了 Jest 性能优化,接下来学习 调试技巧 解决测试问题!

最后更新:2026-03-28