Jenkins声明式流水线:并行批量执行测试用例问题求助
批量并行执行Postman集合的完善方案
针对你遇到的Jenkins执行Postman集合时的资源耗尽、批次中断问题,这里提供两种可落地的解决方案:
1. Groovy脚本原生线程实现(适合自由风格项目)
核心思路是单独捕获每个Newman任务的退出码,不直接让Jenkins感知非零退出码中断流程,最后统一汇总失败状态,同时保持批次内并行、批次间串行的执行逻辑:
// 动态从多仓库拉取的测试集合列表(替换成你的实际获取逻辑) def testCollections = findFiles(glob: "**/*.postman_collection.json").collect { it.path } // 可根据Jenkins节点资源调整(10-20之间) def batchSize = 15 // 线程安全的失败集合存储 def failedCollections = Collections.synchronizedList([]) // 分批处理集合 for (int i = 0; i < testCollections.size(); i += batchSize) { def currentBatch = testCollections.subList(i, Math.min(i + batchSize, testCollections.size())) def batchFutures = [] // 批次内并行执行每个集合 currentBatch.each { collectionPath -> batchFutures.add(Thread.start { println "启动测试集合: ${collectionPath}" // 执行Newman命令并捕获退出码 def newmanProcess = "newman run ${collectionPath}".execute() // 打印输出(可选,便于排查问题) newmanProcess.in.eachLine { line -> println line } newmanProcess.err.eachLine { line -> println "ERROR: ${line}" } newmanProcess.waitFor() def exitCode = newmanProcess.exitValue() if (exitCode != 0) { synchronized(failedCollections) { failedCollections.add(collectionPath) } println "集合 ${collectionPath} 执行失败,退出码: ${exitCode}" } else { println "集合 ${collectionPath} 执行成功" } }) } // 等待当前批次所有任务完成,再执行下一批 batchFutures.each { it.join() } println "=== 第 ${(i / batchSize).toInteger() + 1} 批次执行完成 ===" } // 最后统一判定构建状态 if (!failedCollections.isEmpty()) { println "\n=== 以下测试集合执行失败 ===" failedCollections.each { println "- ${it}" } // 抛出错误标记构建失败 error("测试执行失败,共 ${failedCollections.size()} 个集合未通过") } else { println "\n=== 所有测试集合执行成功 ===" }
关键细节:
- 用
Thread.start实现批次内并行,避免sh命令默认的非零退出码中断逻辑 - 用
Collections.synchronizedList保证多线程环境下失败集合的线程安全 - 打印Newman的标准输出和错误输出,便于后续排查失败原因
- 批次执行完成后再推进到下一批,避免资源瞬间耗尽
2. Jenkins Pipeline Parallel步骤实现(适合Pipeline项目)
如果使用Jenkins Pipeline,官方的parallel步骤更规范,且自带线程管理,结合sh returnStatus: true捕获退出码:
pipeline { agent any stages { stage('批量执行Postman测试') { steps { script { // 动态获取测试集合(替换成你的多仓库拉取逻辑) def testCollections = findFiles(glob: "**/*.postman_collection.json").collect { it.path } def batchSize = 15 def failedCollections = [] for (int i = 0; i < testCollections.size(); i += batchSize) { def currentBatch = testCollections.subList(i, Math.min(i + batchSize, testCollections.size())) def parallelTasks = [:] // 构建当前批次的并行任务 currentBatch.each { collectionPath -> parallelTasks["测试集合: ${collectionPath}"] = { script { def exitCode = sh( script: "newman run ${collectionPath}", returnStatus: true, label: "执行集合 ${collectionPath}" ) if (exitCode != 0) { failedCollections.add(collectionPath) } } } } // 执行当前批次并行任务 parallel parallelTasks echo "=== 第 ${(i / batchSize).toInteger() + 1} 批次执行完成 ===" } // 最终状态判定 if (!failedCollections.isEmpty()) { echo "\n=== 失败集合列表 ===" failedCollections.each { echo "- ${it}" } error "测试失败,共 ${failedCollections.size()} 个集合未通过" } else { echo "\n=== 所有测试集合执行成功 ===" } } } } } }
关键细节:
sh returnStatus: true会返回命令的退出码,而非直接中断Pipelineparallel步骤由Jenkins管理线程,比手动创建线程更稳定- 每个任务带
label,便于在Jenkins控制台查看每个集合的执行状态
额外优化建议
- 动态调整批次大小:根据Jenkins节点的CPU核心数自动计算,比如
def batchSize = Runtime.runtime.availableProcessors() * 2,适配不同节点资源 - Newman性能优化:执行时添加
--silent减少输出,或指定--reporters cli只输出必要信息,降低IO开销 - 资源隔离:如果Jenkins节点支持,可使用
node标签指定专门的测试执行节点,避免影响其他任务
内容的提问来源于stack exchange,提问作者Michael K
相关产品推荐
相关产品推荐

