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,完美满足你的要求。
注意事项
- 无论哪种自定义SoftAssert,必须调用
assertAll(),否则收集到的失败不会被处理,测试会被判定为Passed - 标记为Skipped的方案中,测试方法需要声明
ITestResult参数,TestNG会自动注入当前测试的结果对象 - 如果需要同时处理多个SoftAssert失败,自定义类中的错误收集逻辑已经帮你保存了所有失败记录,最终会统一输出
内容的提问来源于stack exchange,提问作者Krzysztof Walczewski
相关产品推荐
相关产品推荐

