PAYATHON 2026

JUnit 5 与 Cucumber 7.11 如何限制并行测试线程数?

数据阿明

先将 cucumber.execution.parallel.config.strategy 从 dynamic 改为 fixed,再设置目标并行度。如果希望每个实例使用 5 个 Cucumber 工作线程,可以将 fixed.parallelism 和 fixed.max-pool-size 都设为 5,并把配置放在 src/test/resources/junit-platform.properties 中。

当前配置里的 strategy=dynamic 与 fixed.parallelism=1 不配套。使用动态策略时,后者不会决定并行度。这个配置问题可以直接确认,但现有材料不足以确定浏览器崩溃的全部原因。

为什么线程数设置没有生效?

选择了 dynamic,却在调整 fixed 参数

问题中的原始配置是:

cucumber.execution.parallel.enabled=true
cucumber.execution.parallel.config.strategy=dynamic
cucumber.execution.parallel.config.fixed.parallelism=1

dynamic 根据 JVM 可用的处理器数量和 dynamic.factor 计算目标并行度,默认因子为 1。只有选择 fixed 策略时,fixed.parallelism 才用于确定目标并行度。因此,仅修改最后一行无法将线程数限制为 1。Cucumber 引擎并行执行说明

dataproviderthreadcount 不控制 Cucumber 引擎

POM 中有两处 dataproviderthreadcount:项目属性设为 1,Surefire 的 provider 属性设为 2。

这个参数用于配置 TestNG DataProvider 的线程。当前 Runner 使用 @Suite 和 @IncludeEngines("cucumber"),场景由 Cucumber TestEngine 执行,因此这两处配置都不能限制它的并行线程数。Surefire 的 TestNG 配置说明

同样,junit.jupiter.execution.parallel.* 配置针对 Jupiter 测试;当前 Cucumber 场景应使用 cucumber.execution.parallel.* 参数。

如何设置每个实例的并行度?

在 src/test/resources/junit-platform.properties 中配置:

cucumber.publish.quiet=true
cucumber.execution.parallel.enabled=true
cucumber.execution.parallel.config.strategy=fixed
cucumber.execution.parallel.config.fixed.parallelism=5
cucumber.execution.parallel.config.fixed.max-pool-size=5

两项设置分别表示:

  • fixed.parallelism=5:目标并行度为 5。
  • fixed.max-pool-size=5:底层 ForkJoinPool 的最大线程数为 5,该值不能小于目标并行度。

Serenity 官方集成指南使用了 cucumber-junit-platform-engine:7.11.0 与 junit-platform-suite:1.9.2,并展示了这两个参数的组合配置。这里不需要引入 Serenity 依赖或报告插件。对应版本的配置示例

原来的 RunnerTest 可以保留。它已经选择了 Cucumber 引擎、features 资源目录、glue 包及报告插件,限制线程数时无需修改这些内容。

原始问题没有给出 properties 文件的位置。如果这些参数目前写在 cucumber.properties 中,应将上述 JUnit Platform 引擎参数移到 junit-platform.properties。JUnit Platform 支持从 classpath 根目录加载该文件;使用 Maven 标准目录时,对应位置是 src/test/resources/junit-platform.properties。JUnit Platform 配置参数说明

Maven Surefire 应该怎样配置?

对于当前 Cucumber 引擎,可以删除 Surefire 配置中用于限制线程数的 dataproviderthreadcount。项目级同名属性如果没有其他用途,也可以删除。

排查时,可以先将现有 Surefire 的 <configuration> 调整为:

<configuration>
    <testFailureIgnore>false</testFailureIgnore>
    <forkCount>1</forkCount>
    <reuseForks>true</reuseForks>
</configuration>

这里显式使用一个测试 JVM,方便观察单实例行为。将 testFailureIgnore 暂时设为 false,可以让测试失败正常反映到构建结果中。

先只启动一个实例,指定 Runner 运行:

mvn test -Dtest=RunnerTest

不要同时启动其他 CI 实例或 Maven 并行模块构建,否则观察到的总并发会包含其他执行单元。

3 个实例是否会自动得到 15 个并发测试?

只有当 3 个独立实例都运行相应测试、每个实例的目标并行度均为 5,且有足够的可执行场景时,才会形成目标为 3 × 5 = 15 的总场景并发。这个计算还假设每个实例内部没有额外的并发执行层。

原文没有说明“3 个实例”是 3 个 CI 任务、3 个容器,还是 3 个 Surefire fork。需要按实际启动方式分别检查:

  • 如果是 3 个独立 CI 任务或容器,每个任务中的引擎都会应用自己的并行配置。
  • 如果单个任务又启动多个测试 JVM,总并发可能进一步增加。
  • 如果使用 Maven -T 并行构建多个带测试的模块,也会增加同时运行的测试数量。

Surefire 的 forkCount 控制测试 JVM 进程数,不会自动把一个 RunnerTest 中的所有场景均分到多个 JVM。仅将 forkCount 设置为 3,不能保证获得 3 组各 5 个场景的执行效果。Surefire 3.2.5 的 fork 与并行执行说明

恢复原有执行规模时,应先验证单实例,再恢复另外两个实例,并确认各实例的测试分片和输出目录。Runner 中的 JSON、HTML、XML 等报告使用固定路径;如果多个进程共享工作目录,还需要隔离这些输出文件,避免相互覆盖。

修改后如何确认配置生效?

可以先将两个值都设为 1,运行一次,再改为 5。例如先使用:

cucumber.execution.parallel.enabled=true
cucumber.execution.parallel.config.strategy=fixed
cucumber.execution.parallel.config.fixed.parallelism=1
cucumber.execution.parallel.config.fixed.max-pool-size=1

在现有 Cucumber @Before hook 中临时记录场景和线程信息:

import io.cucumber.java.Before;
import io.cucumber.java.Scenario;

@Before
public void logScenarioThread(Scenario scenario) {
    System.out.printf(
        "scenario=%s, thread=%s%n",
        scenario.getName(),
        Thread.currentThread().getName()
    );
}

如果引擎配置看起来仍未生效,按以下顺序检查:

  1. 确认文件名为 junit-platform.properties,且运行时能在测试 classpath 根目录找到它。
  2. 检查 Runner 上是否还有同名 @ConfigurationParameter,以及 IDE、CI 或命令行是否传入了覆盖值。
  3. 检查是否有多个 Runner 选择了同一批 feature,或多个任务同时重复运行相同场景。
  4. 对照执行时间记录场景开始与结束,统计同一时刻的活动场景数。累计出现过多少个线程名,不能直接代表峰值并发。

为什么还需要检查 WebDriver 生命周期?

fixed.parallelism 是目标并行度,fixed.max-pool-size 限制底层线程池大小。Cucumber 文档也说明,最大线程池大小不能严格保证所有情况下的并发场景数量,更不能直接等同于全局浏览器会话上限。Cucumber 关于并行上限的说明

对于 Selenium 测试,还应确认:

  • 并行场景使用各自的 WebDriver,避免共享一个可变的 static WebDriver。
  • 创建成功的浏览器会话在场景结束后执行 quit(),异常路径也能够清理。
  • hook 或重试逻辑不会为同一个场景留下额外会话。
  • 观察的是活动 WebDriver 会话数,而非 Chrome 操作系统进程数。

如果必须严格限制所有实例合计最多存在 15 个浏览器会话,应另外在会话创建与释放处设置容量控制,或使用 Grid 侧的容量管理。单个 JVM 中的限制无法协调另外两个独立实例。

现有材料没有崩溃日志、Grid 容量配置或 WebDriver 管理代码。先修正 dynamic 与 fixed 的配置冲突;如果单实例低并发下仍然崩溃,再依据浏览器和 Grid 日志排查资源占用及会话清理问题。

备注:内容仅供参考。