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

如何在JUnit XML结果中合并Spock @Unroll测试用例

问题:Spock参数化测试JUnit结果无法关联原测试方法

我编写了带@Unroll注解的Spock参数化测试:

@Unroll
def 'A session uses the TLS protocol #enabledProtocolsOfSocket #protocolOfSession'() {
    given:
    // ...

    expect:
    // ...

    where:
    enabledProtocolsOfSocket | protocolOfSession
    ["TLSv1.3"]              | "TLSv1.3"
    ["TLSv1.2", "TLSv1.3"]   | "TLSv1.3"
    ["TLSv1.2"]              | "TLSv1.2"
    ["TLSv1.2", "TLSv1.1"]   | "TLSv1.2"
    ["TLSv1.1", "TLSv1.2"]   | "TLSv1.2"
    ["TLSv1.1"]              | "NONE"
    ["TLSv1"]                | "NONE"
    ["TLSv1", "TLSv1.1"]     | "NONE"
    ["TLSv1.1", "TLSv1"]     | "NONE"
}

该测试生成的JUnit结果XML如下:

<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="com.acme.TlsProtocolSpec" tests="9" skipped="0" failures="0" errors="0" timestamp="2024-07-19T17:23:47" hostname="acme-build5eb650" time="0.403">
  <properties/>
  <testcase name="A session uses the TLS protocol [TLSv1.3] TLSv1.3" classname="com.acme.TlsProtocolSpec" time="0.081"/>
  <testcase name="A session uses the TLS protocol [TLSv1.2, TLSv1.3] TLSv1.3" classname="com.acme.TlsProtocolSpec" time="0.037"/>
  <testcase name="A session uses the TLS protocol [TLSv1.2] TLSv1.2" classname="com.acme.TlsProtocolSpec" time="0.084"/>
  <testcase name="A session uses the TLS protocol [TLSv1.2, TLSv1.1] TLSv1.2" classname="com.acme.TlsProtocolSpec" time="0.07"/>
  <testcase name="A session uses the TLS protocol [TLSv1.1, TLSv1.2] TLSv1.2" classname="com.acme.TlsProtocolSpec" time="0.071"/>
  <testcase name="A session uses the TLS protocol [TLSv1.1] NONE" classname="com.acme.TlsProtocolSpec" time="0.014"/>
  <testcase name="A session uses the TLS protocol [TLSv1] NONE" classname="com.acme.TlsProtocolSpec" time="0.013"/>
  <testcase name="A session uses the TLS protocol [TLSv1, TLSv1.1] NONE" classname="com.acme.TlsProtocolSpec" time="0.013"/>
  <testcase name="A session uses the TLS protocol [TLSv1.1, TLSv1] NONE" classname="com.acme.TlsProtocolSpec" time="0.013"/>
  <system-out><![CDATA[]]></system-out>
  <system-err><![CDATA[]]></system-err>
</testsuite>

问题在于:后续处理结果文件时,无法将这些结果映射到原测试方法——既没有保留带占位符的原测试名称A session uses the TLS protocol #enabledProtocolsOfSocket #protocolOfSession,也没有能关联参数化实例与原方法的结构。

我希望能将所有参数化测试实例归到原测试方法的分组下,类似xUnit的<collection>元素结构:

<collection total="9" passed="9" failed="0" skipped="0" name="Test collection for Com.Acme.SomeClass.Tests.SomeTestName" time="0.484">
    <test name="Com.Acme.SomeClass.Tests.SomeTestName(someParam: &quot;10&quot;)" ...>
    </test>
    <test name="Com.Acme.SomeClass.Tests.SomeTestName(someParam: &quot;11&quot;)" ...>
    </test>
    ...
</collection>

只要能保留原测试方法与所有参数化运行的关联(即一个实例失败则整个测试方法标记为失败),无论是通过JUnit输出配置还是Spock特性实现都可以。

更新说明:
当前使用Gradle 8.5(Groovy 3.0.17,Spock 2.4-M4-groovy-4.0),测试配置如下:

test {
    reports {
        junitXml.required = true
        html.required = true
    }

    useJUnitPlatform()
}

已尝试mergeReruns设置(仅适用于失败重试测试)和Test Report Aggregation Plugin(按类型聚合而非测试用例),均无法解决问题。


解决方案

1. 调整@Unroll命名模板,保留原方法标识

修改测试名称模板,在参数化实例名称中加入原方法的固定前缀,同时让后续处理能通过前缀识别归属:

@Unroll
def 'A session uses the TLS protocol [enabledProtocols: #enabledProtocolsOfSocket, expected: #protocolOfSession]'() {
    // 测试逻辑不变
}

这种方式能让每个参数化实例的名称都包含原方法的核心描述,方便后续脚本或工具按前缀分组关联。

2. 配置Gradle+JUnit平台生成层级化报告

修改Gradle测试配置,启用JUnit平台的层级报告特性,让参数化测试归属于原方法节点下:

test {
    reports {
        junitXml.required = true
        html.required = true
    }

    useJUnitPlatform {
        // 启用标准显示名生成器,保留原方法结构
        configurationParameter 'junit.jupiter.displayname.generator.default', 'org.junit.jupiter.api.DisplayNameGenerator$Standard'
        // 开启详细报告,保留参数化测试的层级关系
        configurationParameter 'junit.platform.reporting.show-details', 'true'
    }

    // Spock专属配置:按原测试方法分组参数化实例
    jvmArgs '-Dspock.reporting.unroll.group-by-feature=true'
}

配置后生成的JUnit XML会将同一原方法的参数化实例归为逻辑分组,classname字段会包含原方法的完整标识,便于关联映射。

3. 移除@Unroll,合并参数化结果

如果不需要单独查看每个参数实例的执行详情,可以直接移除@Unroll注解。此时Spock会将整个参数化测试作为一个测试用例执行:

  • 所有参数实例的结果合并到原方法名下
  • 只要有一个实例失败,整个测试方法直接标记为失败
  • JUnit XML中只会生成一个对应原方法的testcase节点,包含整体执行结果

4. 自定义XML后处理脚本

如果上述配置无法满足需求,可以编写Gradle任务手动修改生成的JUnit XML:

  1. 遍历测试输出目录下的XML文件
  2. 按测试名称的共性前缀(原方法描述)对testcase元素分组
  3. 重新生成包含自定义<collection>节点的XML结构,将同组实例归入对应集合

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 16:24:53