如何配置Mocha限制异步it测试用例的并发执行数量?
首先得澄清一个关键点:Mocha默认是串行执行单个测试文件里的所有it用例——也就是一个测试跑完,才会启动下一个。所以你遇到的失败,大概率不是Mocha并发执行用例导致的,而是短时间内(哪怕串行)发起100万个HTTP请求,耗尽了系统资源(比如TCP连接池、文件描述符),或者测试用例数量太大导致内存溢出。不过针对你的需求,我整理了几种可行的方案:
一、Mocha原生的并行控制(针对多文件场景)
如果你是跨多个测试文件并行跑测试,Mocha从v8.0开始支持--parallel选项,配合--jobs可以限制同时运行的测试文件数量。比如:
mocha --parallel --jobs 4
这个命令会让Mocha最多同时跑4个测试文件,但单个文件内的测试依然是串行的。不过你的场景是单文件生成100万个用例,这个选项帮不上太大忙,得看下面的方案。
二、单文件内控制并发的实用方案
1. 批量分组测试,用Promise.all控制并发
既然没法直接控制it的执行节奏,不如把测试用例分组,每个it执行一组请求,用Promise.all限制每组的并发数。这样既控制了资源占用,又能在Mocha报告里看到分组的结果:
const assert = require('assert'); const func = require('./your-func'); // 自定义每组的并发请求数,根据系统性能调整 const CONCURRENCY_LIMIT = 10; const TOTAL_TESTS = 1000000; describe('批量HTTP请求测试', function() { // 关闭超时限制,因为批量测试耗时会很长 this.timeout(0); const groupCount = Math.ceil(TOTAL_TESTS / CONCURRENCY_LIMIT); for (let groupIdx = 0; groupIdx < groupCount; groupIdx++) { it(`测试组 ${groupIdx + 1}`, async function() { const start = groupIdx * CONCURRENCY_LIMIT; const end = Math.min(start + CONCURRENCY_LIMIT, TOTAL_TESTS); const testPromises = []; for (let i = start; i < end; i++) { testPromises.push((async () => { const x = await func(...); // 这里写你的断言逻辑 assert.ok(/* 对x的判断条件 */); })()); } // 等待这一组的所有请求完成 await Promise.all(testPromises); }); } });
2. 复用HTTP连接,减少资源消耗
很多HTTP客户端库(比如axios、node-fetch)默认支持连接复用(keep-alive),但如果是用Node.js原生http/https模块,记得手动配置Agent来限制同时打开的socket数量:
const http = require('http'); // 配置连接复用,同时限制最大socket数 const httpAgent = new http.Agent({ keepAlive: true, maxSockets: 10 // 和之前的并发数对应 }); async function func() { return new Promise((resolve, reject) => { http.get({ hostname: '你的远程服务地址', agent: httpAgent }, (res) => { // 处理响应逻辑 resolve(/* 处理后的结果 */); }).on('error', reject); }); }
这样能大幅减少TCP握手的开销,避免系统资源被快速耗尽。
3. 用--delay手动控制测试用例的执行节奏
如果你想保留每个测试用例单独的it条目(方便看单个用例的失败情况),可以用Mocha的--delay选项,手动管理一个测试队列,控制并发执行的数量:
const assert = require('assert'); const func = require('./your-func'); const CONCURRENCY_LIMIT = 10; const TOTAL_TESTS = 1000000; let currentTestIdx = 0; let activeTests = 0; describe('可控并发的单个测试用例', function() { this.timeout(0); // 递归启动下一批测试 function runNextTest() { while (activeTests < CONCURRENCY_LIMIT && currentTestIdx < TOTAL_TESTS) { const testNum = currentTestIdx++; activeTests++; it(`test case ${testNum}`, async function() { const x = await func(...); assert.ok(/* 对x的判断条件 */); activeTests--; // 当前测试完成后,启动下一个 runNextTest(); }); } // 所有测试都添加完成且执行完毕后,手动触发Mocha的run if (currentTestIdx >= TOTAL_TESTS && activeTests === 0) { run(); } } // 启动测试队列 runNextTest(); });
运行时记得加上--delay参数:
mocha --delay
这个方法既能让每个测试用例单独显示在报告里,又能严格控制并发数。
4. 减少测试用例数量(最务实的方案)
100万个测试用例实在太多了——如果你的测试逻辑是重复的(比如只是参数不同),完全可以用抽样测试或者参数化测试来覆盖场景,比如只测边界值、随机抽1000个用例:
const assert = require('assert'); const func = require('./your-func'); // 随机抽样1000个用例,足够覆盖大部分场景 const SAMPLE_SIZE = 1000; for (let i = 0; i < SAMPLE_SIZE; i++) { const randomTestNum = Math.floor(Math.random() * 1000000); it(`test case ${randomTestNum}`, async function() { const x = await func(...); assert.ok(/* 对x的判断条件 */); }); }
这样能大幅降低资源消耗,同时不影响测试的有效性。
内容的提问来源于stack exchange,提问作者goodvibration

