如何在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多次启动,每次执行一个批次:
- 编写启动脚本
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); }
- 在测试文件中读取环境变量,生成对应批次的用例:
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
相关产品推荐
相关产品推荐

