You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Jest中高效测试超10万条测试用例?

解决Jest大量测试用例内存溢出的分批续跑方案

核心思路

通过分批生成/读取测试用例、记录已执行位置、每批执行后清理内存的方式,避免一次性加载所有用例导致的内存溢出,同时实现断点续跑。

步骤1:改造笛卡尔积生成逻辑,支持分批生成

不需要一次性生成所有笛卡尔积组合,而是按需按区间生成对应批次的用例,从根源上减少内存占用。

改造后的分批生成函数示例:

// 计算笛卡尔积总条数
const getCartesianTotal = (...arrays) => arrays.reduce((total, arr) => total * arr.length, 1);

// 生成指定区间的笛卡尔积组合(start到end-1)
function generateCartesianBatch(arrays, start, end) {
  const result = [];
  const total = getCartesianTotal(...arrays);
  const endIdx = Math.min(end, total);
  
  for (let i = start; i < endIdx; i++) {
    let current = i;
    const combination = [];
    // 反向遍历数组,计算当前索引对应的组合
    for (let j = arrays.length - 1; j >= 0; j--) {
      const arr = arrays[j];
      combination.unshift(arr[current % arr.length]);
      current = Math.floor(current / arr.length);
    }
    result.push(combination);
  }
  return result;
}

步骤2:用文件记录已执行的用例索引

通过本地文件保存最后执行完成的索引,每次启动测试时先读取这个文件,从下一个索引开始执行。

const fs = require('fs');
const progressFile = './test-progress.json';

// 读取执行进度
function getProgress() {
  if (fs.existsSync(progressFile)) {
    return JSON.parse(fs.readFileSync(progressFile, 'utf8'));
  }
  return { lastIndex: 0 };
}

// 更新执行进度
function updateProgress(lastIndex) {
  fs.writeFileSync(progressFile, JSON.stringify({ lastIndex }, null, 2));
}

步骤3:分批执行测试并清理内存

提供两种方案,按需选择:

方案A:单进程分批执行(适合批次内用例数不会触发溢出)

const testArrays = [/* 你的测试数据数组 */];
const batchSize = 50000;
const totalCases = getCartesianTotal(...testArrays);
const progress = getProgress();
let currentIndex = progress.lastIndex;

while (currentIndex < totalCases) {
  const endIndex = currentIndex + batchSize;
  const testBatch = generateCartesianBatch(testArrays, currentIndex, endIndex);
  
  // 执行当前批次的测试
  test.each(testBatch)('测试组合: %o', (...args) => {
    // 你的测试逻辑
    expect(yourTestFunction(...args)).toBe(true);
  });
  
  // 更新进度
  currentIndex = endIndex;
  updateProgress(currentIndex);
  
  // 手动触发垃圾回收(需Node.js启动时加 --expose-gc 参数)
  if (global.gc) {
    global.gc();
  }
}

方案B:多进程分批执行(更彻底的内存清理)

如果单进程分批仍有内存残留,通过脚本控制Jest多次启动,每次执行一个批次:

  1. 编写启动脚本run-tests-batch.js:
const { spawnSync } = require('child_process');
const fs = require('fs');

const testArrays = [/* 你的测试数据数组 */];
const batchSize = 50000;
const totalCases = getCartesianTotal(...testArrays);
const progressFile = './test-progress.json';

let progress = { lastIndex: 0 };
if (fs.existsSync(progressFile)) {
  progress = JSON.parse(fs.readFileSync(progressFile, 'utf8'));
}

const currentIndex = progress.lastIndex;
if (currentIndex >= totalCases) {
  console.log('所有测试用例已执行完成');
  fs.unlinkSync(progressFile); // 可选:完成后删除进度文件
  process.exit(0);
}

const endIndex = Math.min(currentIndex + batchSize, totalCases);
// 将当前批次的索引传入测试文件
process.env.TEST_START_INDEX = currentIndex;
process.env.TEST_END_INDEX = endIndex;

// 启动Jest执行当前批次
const result = spawnSync('npx', ['jest', '--runInBand'], { stdio: 'inherit' });

if (result.status === 0) {
  // 测试通过,更新进度
  updateProgress(endIndex);
  // 递归调用自身,执行下一批次
  spawnSync('node', ['run-tests-batch.js'], { stdio: 'inherit' });
} else {
  console.log(`批次执行失败,当前进度停留在索引 ${currentIndex}`);
  process.exit(1);
}
  1. 在测试文件中读取环境变量,生成对应批次的用例:
const testArrays = [/* 你的测试数据数组 */];
const startIndex = parseInt(process.env.TEST_START_INDEX || 0);
const endIndex = parseInt(process.env.TEST_END_INDEX || 50000);

const testBatch = generateCartesianBatch(testArrays, startIndex, endIndex);

test.each(testBatch)('测试组合: %o', (...args) => {
  // 你的测试逻辑
  expect(yourTestFunction(...args)).toBe(true);
});

注意事项

  • 启动Node.js时如果需要手动触发垃圾回收,需添加参数node --expose-gc your-test-file.js
  • 多进程方案中,--runInBand参数确保Jest单进程执行,避免内存叠加
  • 进度文件可以放在项目目录下,注意添加到.gitignore避免提交到版本控制

内容的提问来源于stack exchange,提问作者Daniel Haven

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 10:52:42