如何标记Cucumber JUnit步骤失败但继续执行,实现软断言?
我来帮你梳理下这个问题的核心点和对应的解决方案,毕竟在Cucumber里做软断言时,很容易遇到JUnit失败计数不生效的坑:
1. 是什么触发了JUnit的失败计数器?
JUnit的失败计数器本质是通过**未被捕获的AssertionError(或测试相关的失败异常)**来触发的。当你用try-catch把AssertTrue抛出的AssertionError手动捕获后,JUnit的测试运行器就感知不到这个失败了,自然不会把它计入失败统计,反而会判定该步骤执行成功。
2. 如何在不抛出AssertionError的情况下手动标记JUnit失败?
这里有两种靠谱的方案,既能保留软断言的特性(不中断执行),又能让JUnit正确统计失败:
方案一:使用AssertJ的软断言库(推荐)
AssertJ提供了开箱即用的SoftAssertions类,专门用来收集多个断言的失败信息,最后统一抛出,完美适配JUnit和Cucumber:
import org.assertj.core.api.SoftAssertions; import io.cucumber.java.After; import io.cucumber.java.en.Then; public class YourStepDefinitions { // 初始化软断言实例 private final SoftAssertions softAssertions = new SoftAssertions(); @Then("验证页面上的多个元素状态") public void verifyMultipleElementStates() { // 第一个断言:验证元素可见 softAssertions.assertThat(element.isDisplayed()) .as("元素[用户名输入框]应该可见") .isTrue(); // 第二个断言:验证文本内容,即使第一个失败,这个也会执行 softAssertions.assertThat(element.getText()) .as("页面标题应该为'首页'") .isEqualTo("首页"); // 更多断言... } // 在场景结束时统一校验所有断言 @After public void afterScenario() { softAssertions.assertAll(); } }
这样做的好处:
- 所有断言都会执行,不会因为某个断言失败中断步骤
- 最后
assertAll()会抛出汇总的AssertionError,JUnit会正确统计失败次数 - Cucumber报告里会详细显示每个失败的预期值、实际值和自定义描述
方案二:自定义JUnit监听器/规则
如果不想引入第三方库,可以自己实现一个监听器来收集失败信息:
import org.junit.rules.TestWatcher; import org.junit.runner.Description; import java.util.ArrayList; import java.util.List; import static org.junit.Assert.fail; public class SoftAssertRule extends TestWatcher { private final List<String> failureMessages = new ArrayList<>(); public void addFailure(String message) { failureMessages.add(message); } @Override protected void finished(Description description) { if (!failureMessages.isEmpty()) { StringBuilder combinedMessage = new StringBuilder("共") .append(failureMessages.size()) .append("个断言失败:\n"); for (String msg : failureMessages) { combinedMessage.append("- ").append(msg).append("\n"); } fail(combinedMessage.toString()); } } }
然后在步骤类里使用这个规则:
import org.junit.Rule; import io.cucumber.java.en.Then; public class YourStepDefinitions { @Rule public SoftAssertRule softAssertRule = new SoftAssertRule(); @Then("执行多个断言") public void executeMultipleAssertions() { try { assertTrue("第一个条件不满足", someCondition.isTrue()); } catch (AssertionError e) { softAssertRule.addFailure(e.getMessage()); } try { assertEquals("数值不匹配", 5, actualValue); } catch (AssertionError e) { softAssertRule.addFailure(e.getMessage()); } } }
这个方案需要手动捕获每个断言的错误,然后添加到失败列表,最后由监听器统一抛出失败。
3. Cucumber Runner是否有选项让测试失败时不终止执行?
Cucumber默认就不会因为单个步骤失败终止整个测试套件(只会终止当前场景),如果想让当前场景在步骤失败后继续执行后续步骤,核心还是要靠软断言的实现(上面的两种方案)。
另外,Cucumber Runner可以设置failFast = false(这是默认值),确保一个场景失败后不会终止其他场景的执行:
import io.cucumber.junit.Cucumber; import io.cucumber.junit.CucumberOptions; import org.junit.runner.RunWith; @RunWith(Cucumber.class) @CucumberOptions( features = "src/test/resources/features", glue = "com.yourpackage.stepdefs", failFast = false // 默认就是false,显式设置更清晰 ) public class TestRunner { }
关于Cucumber报告的失败信息
不管用哪种方案,只要最后抛出了包含详细信息的AssertionError,Cucumber的HTML/JSON报告都会自动捕获这些信息,展示每个失败的预期值、实际值和上下文描述,完全满足你的需求。
内容的提问来源于stack exchange,提问作者Sorin D.

