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

如何用TestNG记录测试历史并生成Java API测试失败统计报告?

简单实现方案:基于JUnit 5扩展(推荐)

这是当前Java生态最常用的测试方案,不用额外折腾工具,直接在IntelliJ里就能实现你要的重复执行+失败统计需求:

步骤1:给测试方法添加重复执行注解

用JUnit 5的@RepeatedTest(100)让目标测试自动跑100次,方法级注解可以灵活控制每个测试的执行次数:

import org.junit.jupiter.api.RepeatedTest;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class MyApiTests {

    @RepeatedTest(100)
    void myTest() {
        // 替换成你的API测试逻辑:发送请求、断言响应
        int actualStatusCode = callApi();
        assertEquals(200, actualStatusCode);
    }

    // 模拟API请求(实际项目替换为真实调用)
    private int callApi() {
        double random = Math.random();
        if (random < 0.25) return 304;
        if (random < 0.30) return 404;
        return 200;
    }
}

步骤2:自定义失败统计扩展

写一个JUnit 5扩展类,监听测试失败事件,自动收集每个测试的失败原因、次数和堆栈信息:

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

public class FailureStatisticsExtension implements TestExecutionExceptionHandler {

    // 存储统计数据:键为「测试方法名+失败原因」组合,值为失败详情
    private static final Map<String, FailureRecord> failureRecords = new HashMap<>();

    @Override
    public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
        String testName = context.getRequiredTestMethod().getName();
        String failureReason = throwable.getMessage();
        String stackTrace = getStackTraceAsString(throwable);

        // 生成唯一键,确保同一测试的不同失败原因分开统计
        String key = testName + "|" + failureReason;
        failureRecords.compute(key, (k, record) -> {
            if (record == null) {
                return new FailureRecord(testName, failureReason, 1, stackTrace);
            } else {
                record.incrementCount();
                return record;
            }
        });

        // 继续抛出异常,不影响测试的失败状态
        throw throwable;
    }

    // 将异常堆栈转为字符串
    private String getStackTraceAsString(Throwable throwable) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        throwable.printStackTrace(pw);
        return sw.toString();
    }

    // 测试全部完成后生成指定格式的报告
    public static void generateReport() {
        System.out.println("\n=== 测试失败统计报告 ===");
        for (FailureRecord record : failureRecords.values()) {
            System.out.printf("failingTest: %s, failureReason: %s, timesOccurred: %d/100, stackTrace: %s%n",
                    record.testName, record.failureReason, record.count, record.stackTrace);
        }
    }

    // 内部类存储单条失败记录
    private static class FailureRecord {
        String testName;
        String failureReason;
        int count;
        String stackTrace;

        public FailureRecord(String testName, String failureReason, int count, String stackTrace) {
            this.testName = testName;
            this.failureReason = failureReason;
            this.count = count;
            this.stackTrace = stackTrace;
        }

        public void incrementCount() {
            this.count++;
        }
    }
}

步骤3:注册扩展并生成报告

在测试类上注册扩展,并用@AfterAll触发报告生成:

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.junit.jupiter.api.Assertions.assertEquals;

@ExtendWith(FailureStatisticsExtension.class)
public class MyApiTests {

    @RepeatedTest(100)
    void myTest() {
        int actualStatusCode = callApi();
        assertEquals(200, actualStatusCode);
    }

    private int callApi() {
        double random = Math.random();
        if (random < 0.25) return 304;
        if (random < 0.30) return 404;
        return 200;
    }

    @AfterAll
    static void generateFailureReport() {
        FailureStatisticsExtension.generateReport();
    }
}
备选方案:基于TestNG

如果你项目用的是TestNG,也可以用类似逻辑实现:

import org.testng.ITestResult;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.Test;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

public class MyApiTestNGTests {

    private static final Map<String, FailureRecord> failureRecords = new HashMap<>();

    @Test(invocationCount = 100)
    void myTest() {
        int actualStatusCode = callApi();
        assert actualStatusCode == 200 : "Expected 200 but got " + actualStatusCode;
    }

    private int callApi() {
        double random = Math.random();
        if (random < 0.25) return 304;
        if (random < 0.30) return 404;
        return 200;
    }

    // 监听测试失败事件
    public void onTestFailure(ITestResult result) {
        String testName = result.getName();
        Throwable throwable = result.getThrowable();
        String failureReason = throwable.getMessage();
        String stackTrace = getStackTraceAsString(throwable);

        String key = testName + "|" + failureReason;
        failureRecords.compute(key, (k, record) -> {
            if (record == null) {
                return new FailureRecord(testName, failureReason, 1, stackTrace);
            } else {
                record.incrementCount();
                return record;
            }
        });
    }

    private String getStackTraceAsString(Throwable throwable) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        throwable.printStackTrace(pw);
        return sw.toString();
    }

    @AfterSuite
    void generateReport() {
        System.out.println("\n=== 测试失败统计报告 ===");
        for (FailureRecord record : failureRecords.values()) {
            System.out.printf("failingTest: %s, failureReason: %s, timesOccurred: %d/100, stackTrace: %s%n",
                    record.testName, record.failureReason, record.count, record.stackTrace);
        }
    }

    private static class FailureRecord {
        String testName;
        String failureReason;
        int count;
        String stackTrace;

        public FailureRecord(String testName, String failureReason, int count, String stackTrace) {
            this.testName = testName;
            this.failureReason = failureReason;
            this.count = count;
            this.stackTrace = stackTrace;
        }

        public void incrementCount() {
            this.count++;
        }
    }
}

运行测试后,控制台会直接输出你要求格式的统计报告。如果需要把报告写入文件,只需要把System.out.println替换为文件写入逻辑即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:25:43