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

TestNG:如何将SoftAssert失败标记为非Failure类型

这个需求很实用!默认情况下TestNG的SoftAssert会把所有断言失败统一归类为Failures,但要实现你想要的「HardAssert失败算Failures,SoftAssert失败算Errors/Skipped」的区分效果,我们可以通过自定义SoftAssert子类来改写它的失败处理逻辑,下面给你两种可行的实现方案:

核心逻辑铺垫

先明确TestNG里三类结果的定义:

  • Failures:明确的断言失败(比如HardAssert的Assert.assertEquals不通过)
  • Errors:测试过程中抛出的未捕获异常
  • Skipped:测试被主动标记为跳过(比如依赖前置测试失败、主动调用跳过API)

我们的思路就是:改写SoftAssert的失败收集和最终处理逻辑,把原本会抛出AssertionError(被归类为Failures)的操作,换成抛出普通异常(转为Errors)或者主动标记测试跳过(转为Skipped)。


方案1:将SoftAssert失败标记为Errors

通过自定义SoftAssert子类,在assertAll()时抛出普通RuntimeException而非默认的AssertionError,TestNG就会把这类异常归类为Errors。

自定义ErrorSoftAssert类

import org.testng.asserts.Assertion;
import org.testng.asserts.IAssert;
import org.testng.collections.Maps;

import java.util.Map;

public class ErrorSoftAssert extends Assertion {
    // 收集所有SoftAssert的失败记录
    private final Map<IAssert<?>, AssertionError> errorRecords = Maps.newLinkedHashMap();

    @Override
    protected void doAssert(IAssert<?> assertion) {
        try {
            assertion.doAssert();
        } catch (AssertionError error) {
            errorRecords.put(assertion, error);
        }
    }

    public void assertAll() {
        if (!errorRecords.isEmpty()) {
            StringBuilder errorMsg = new StringBuilder("SoftAssert断言失败(标记为Error):\n");
            for (Map.Entry<IAssert<?>, AssertionError> entry : errorRecords.entrySet()) {
                errorMsg.append("- 断言描述:").append(entry.getKey().getMessage()).append("\n");
                errorMsg.append("- 失败原因:").append(entry.getValue().getMessage()).append("\n");
            }
            // 抛出普通RuntimeException,让TestNG归类为Error
            throw new RuntimeException(errorMsg.toString());
        }
    }
}

测试用例示例

import org.testng.Assert;
import org.testng.annotations.Test;

public class AssertionDemoTest {

    @Test
    public void testHardAssertFailure() {
        // HardAssert失败,会被标记为Failures
        Assert.assertEquals(1, 2, "HardAssert数值不匹配");
    }

    @Test
    public void testSoftAssertAsError() {
        ErrorSoftAssert softAssert = new ErrorSoftAssert();
        softAssert.assertEquals("foo", "bar", "SoftAssert字符串不匹配");
        // 必须调用assertAll才会触发失败处理
        softAssert.assertAll();
    }
}

运行后结果会是:Tests run: 2, Failures: 1, Errors: 1, Skipped: 0,完全符合你的需求。


方案2:将SoftAssert失败标记为Skipped

要实现Skipped,我们需要在测试方法中注入ITestResult对象,然后在assertAll()时主动将测试结果标记为跳过状态。

自定义SkipSoftAssert类

import org.testng.ITestResult;
import org.testng.asserts.Assertion;
import org.testng.asserts.IAssert;
import org.testng.collections.Maps;

import java.util.Map;

public class SkipSoftAssert extends Assertion {
    private final Map<IAssert<?>, AssertionError> errorRecords = Maps.newLinkedHashMap();
    private final ITestResult testResult;

    // 构造时传入当前测试的ITestResult对象
    public SkipSoftAssert(ITestResult testResult) {
        this.testResult = testResult;
    }

    @Override
    protected void doAssert(IAssert<?> assertion) {
        try {
            assertion.doAssert();
        } catch (AssertionError error) {
            errorRecords.put(assertion, error);
        }
    }

    public void assertAll() {
        if (!errorRecords.isEmpty()) {
            StringBuilder skipMsg = new StringBuilder("SoftAssert断言失败,标记为Skipped:\n");
            for (Map.Entry<IAssert<?>, AssertionError> entry : errorRecords.entrySet()) {
                skipMsg.append("- 断言描述:").append(entry.getKey().getMessage()).append("\n");
                skipMsg.append("- 失败原因:").append(entry.getValue().getMessage()).append("\n");
            }
            // 主动将测试结果设置为SKIP
            testResult.setStatus(ITestResult.SKIP);
            testResult.setThrowable(new AssertionError(skipMsg.toString()));
        }
    }
}

测试用例示例

import org.testng.Assert;
import org.testng.ITestResult;
import org.testng.annotations.Test;

public class AssertionDemoTest {

    @Test
    public void testHardAssertFailure() {
        Assert.assertEquals(1, 2, "HardAssert数值不匹配");
    }

    @Test
    public void testSoftAssertAsSkip(ITestResult testResult) {
        SkipSoftAssert softAssert = new SkipSoftAssert(testResult);
        softAssert.assertEquals(3, 4, "SoftAssert数值不匹配");
        softAssert.assertAll();
    }
}

运行后结果会是:Tests run: 2, Failures: 1, Errors: 0, Skipped: 1,完美满足你的要求。


注意事项
  1. 无论哪种自定义SoftAssert,必须调用assertAll(),否则收集到的失败不会被处理,测试会被判定为Passed
  2. 标记为Skipped的方案中,测试方法需要声明ITestResult参数,TestNG会自动注入当前测试的结果对象
  3. 如果需要同时处理多个SoftAssert失败,自定义类中的错误收集逻辑已经帮你保存了所有失败记录,最终会统一输出

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:57:56