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

如何将Karate测试报告生成与执行分离并合并多Runner报告?

Karate报告合并与执行逻辑分离方案咨询

我们近期对测试Runner进行了更新,可将多个测试执行合并为单个测试任务,当前已实现合并生成单份Cucumber报告,但Karate报告仍与单个Runner绑定。希望了解:是否可将Karate报告的生成逻辑与测试执行分离(类似Cucumber报告的实现方式),或提供方法将子目录中的所有Karate汇总报告合并到父目录中?


现有实现代码

总Runner代码(EquipmentRunner_AllInOne)

package net.equipment.runners;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import net.masterthought.cucumber.Configuration;
import net.masterthought.cucumber.ReportBuilder;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.Test;

public class EquipmentRunner_AllInOne {

    private static final String serviceName = "EquipmentService";
    private static final String reportRoot = "target/" + serviceName + "/";
    private static final String dtStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());

    @Test
    public void testParallel() {
        _EquipmentRunnerDomainV1 v1 = new _EquipmentRunnerDomainV1()
                .reportDir(reportRoot + dtStamp + "/domain/v1")
                .buildDir(reportRoot + dtStamp);
        v1.concatRunner();

        _EquipmentRunnerDomainV2 v2 = new _EquipmentRunnerDomainV2()
                .reportDir(reportRoot + dtStamp + "/domain/v2")
                .buildDir(reportRoot + dtStamp);
        v2.concatRunner();

        // _EquipmentRunnerDomainV3 v3 = new _EquipmentRunnerDomainV3()
        // .reportDir(reportRoot + dtStamp + "/domain/v3")
        // .buildDir(reportRoot + dtStamp);
        // v3.concatRunner();

        // _EquipmentRunnerDomainV4 v4 = new _EquipmentRunnerDomainV4()
        // .reportDir(reportRoot + dtStamp + "/domain/v4")
        // .buildDir(reportRoot + dtStamp);
        // v4.concatRunner();

        // _EquipmentRunnerGraph graph = new _EquipmentRunnerGraph()
        // .reportDir(reportRoot + dtStamp + "/graph")
        // .buildDir(reportRoot + dtStamp);
        // Graph.concatRunner();

        Collection<File> jsonFiles = FileUtils.listFiles(new File(reportRoot + dtStamp), new String[] { "json" }, true);
        List<String> jsonPaths = new ArrayList<>(jsonFiles.size());
        jsonFiles.forEach(file -> jsonPaths.add(file.getAbsolutePath()));
        Configuration config = new Configuration(new File("target"), serviceName);
        ReportBuilder reportBuilder = new ReportBuilder(jsonPaths, config);
        reportBuilder.generateReports();
    }
}

单个子Runner代码(_EquipmentRunnerDomainV1)

package net.equipment.runners;

import com.intuit.karate.Results;
import com.intuit.karate.Runner;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import net.masterthought.cucumber.Configuration;
import net.masterthought.cucumber.ReportBuilder;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class _EquipmentRunnerDomainV1 {

    private static final String serviceName = "EquipmentService";
    private static final String reportRoot = "target/" + serviceName + "/";
    private static final Integer cores = Runtime.getRuntime().availableProcessors();

    public String _path = "classpath:net/equipment/test/api/domain/v1";
    public String[] _tags = { "@regression", "@hopper" };
    public String _karateEnv = "dev";
    public String _buildDir = reportRoot + "/";
    public String _reportDir = reportRoot + "/domain/v1";

    public _EquipmentRunnerDomainV1() {
    }

    public _EquipmentRunnerDomainV1 path(String _path) {
        this._path = _path;
        return this;
    }

    public _EquipmentRunnerDomainV1 tags(String[] _tags) {
        this._tags = _tags;
        return this;
    }

    public _EquipmentRunnerDomainV1 karateEnv(String _karateEnv) {
        this._karateEnv = _karateEnv;
        return this;
    }

    public _EquipmentRunnerDomainV1 buildDir(String _buildDir) {
        this._buildDir = _buildDir;
        return this;
    }

    public _EquipmentRunnerDomainV1 reportDir(String _reportDir) {
        this._reportDir = _reportDir;
        return this;
    }

    // Allows concatenated execution of runner
    @Test
    public void concatRunner() {
        Results results = Runner
                .path(_path)
                .tags(_tags)
                .karateEnv(_karateEnv)
                .outputCucumberJson(true)
                .outputHtmlReport(true)
                .outputJunitXml(true)
                .buildDir(_buildDir)
                .reportDir(_reportDir)
                .systemProperty("karate.dsRow", "1") 
                .parallel(cores);

        assertEquals(0, results.getFailCount(), results.getErrorMessages());
    }

    // Allows manual execution of runner with results
    @Test
    public void testParallel() {
        Results results = Runner
                .path("classpath:net/equipment/test/api/domain/v1")
                .tags("@regression", "@hopper")
                .karateEnv(_karateEnv)
                .outputCucumberJson(true)
                .outputHtmlReport(true)
                .outputJunitXml(true)
                .reportDir(_reportDir)
                .systemProperty("karate.dsRow", "1") 
                .parallel(cores);

        generateReport(results.getReportDir());
        assertEquals(0, results.getFailCount(), results.getErrorMessages());
    }

    public static void generateReport(String karateOutputPath) {
        Collection<File> jsonFiles = FileUtils.listFiles(new File(karateOutputPath), new String[] { "json" }, true);
        List<String> jsonPaths = new ArrayList<>(jsonFiles.size());
        jsonFiles.forEach(file -> jsonPaths.add(file.getAbsolutePath()));
        Configuration config = new Configuration(new File("target"), serviceName);
        ReportBuilder reportBuilder = new ReportBuilder(jsonPaths, config);
        reportBuilder.generateReports();
    }
}

解决方案

1. 分离Karate报告生成逻辑(推荐)

和Cucumber报告的实现逻辑对齐,让测试执行只生成JSON结果,统一在总Runner中生成合并报告:

  • 修改子Runner:将outputHtmlReport(true)改为outputHtmlReport(false),关闭单个Runner的自动HTML报告生成,保留outputCucumberJson(true)用于生成JSON结果
  • 扩展总Runner:在总Runner的testParallel方法末尾添加代码,收集所有子Runner的Karate JSON结果,调用Karate的ReportGenerator生成合并后的HTML报告:
// 合并生成Karate总报告
File karateReportDir = new File(reportRoot + dtStamp + "/karate-merged-report");
List<String> karateJsonPaths = new ArrayList<>();
// 收集所有子目录下的Karate JSON文件(默认前缀为karate-)
FileUtils.listFiles(new File(reportRoot + dtStamp), new String[]{"json"}, true).forEach(file -> {
    if (file.getName().startsWith("karate-")) {
        karateJsonPaths.add(file.getAbsolutePath());
    }
});
// 初始化Karate报告生成器并生成合并报告
com.intuit.karate.report.ReportGenerator generator = new com.intuit.karate.report.ReportGenerator(karateJsonPaths, karateReportDir, serviceName);
generator.generateHtml();

2. 合并已生成的子目录Karate报告

如果已经生成了子目录的Karate报告,最可靠的方式是基于JSON源重新生成合并报告(即上述方案)。若需直接合并HTML内容,可通过以下方式:

  • 遍历所有子目录的Karate报告HTML,提取测试结果片段
  • 自定义HTML模板,将所有片段整合到一份总报告中
    不过这种方式维护成本高,不如基于JSON重新生成灵活可靠。

注意事项

  • 确保子Runner的outputCucumberJson(true)保持开启,这是生成合并报告的数据基础
  • 调整文件收集逻辑时,注意区分Karate JSON和Cucumber JSON(Karate默认生成的JSON文件名以karate-开头)
  • 若使用Karate版本较旧,需确认ReportGenerator类的包路径和方法是否兼容

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 17:42:02