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

Cucumber v7.18.1:如何运行时动态更新步骤状态而非修改JSON报告

解决Cucumber软断言步骤状态不更新问题

针对你用Cucumber-java v7.18.1遇到的软断言步骤状态显示异常问题,这里提供一套在测试运行时动态修改步骤状态的方案,无需事后修改JSON报告:

核心思路

  1. 自定义软断言类,不仅收集断言错误,还关联错误对应的执行步骤ID
  2. 通过钩子和ThreadLocal存储当前执行的步骤ID,确保并发测试安全
  3. 利用Cucumber的TestCaseState标记出错步骤为失败,替代事后修改报告的方式

具体实现

1. 自定义软断言类

继承AssertJ的SoftAssertions,重写错误收集方法,同时关联当前步骤ID:

import org.assertj.core.api.SoftAssertions;
import org.assertj.core.api.SoftAssertionError;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CucumberSoftAssertions extends SoftAssertions {
    // 线程本地存储当前步骤ID,支持多线程并发测试
    private static final ThreadLocal<String> currentStepId = new ThreadLocal<>();
    // 存储步骤ID与对应断言错误的映射
    private final Map<String, List<AssertionError>> stepErrors = new HashMap<>();

    public static void setCurrentStepId(String stepId) {
        currentStepId.set(stepId);
    }

    public static void clearCurrentStepId() {
        currentStepId.remove();
    }

    @Override
    protected void onAssertionErrorCollected(AssertionError error) {
        String stepId = currentStepId.get();
        if (stepId != null) {
            stepErrors.computeIfAbsent(stepId, k -> new ArrayList<>()).add(error);
        }
        super.onAssertionErrorCollected(error);
    }

    public Map<String, List<AssertionError>> getStepErrors() {
        return stepErrors;
    }

    @Override
    public void assertAll() throws SoftAssertionError {
        try {
            super.assertAll();
        } finally {
            stepErrors.clear();
        }
    }
}

2. 步骤钩子:记录当前步骤ID

在每个步骤执行前,获取并存储当前步骤的唯一ID:

import io.cucumber.java.BeforeStep;
import io.cucumber.core.runtime.ScenarioImpl;
import io.cucumber.java.Scenario;
import io.cucumber.plugin.event.PickleStepTestStep;
import io.cucumber.plugin.event.TestStep;

public class StepHook {

    @BeforeStep
    public void beforeStep(Scenario scenario) {
        ScenarioImpl scenarioImpl = (ScenarioImpl) scenario;
        TestStep currentStep = scenarioImpl.getCurrentTestStep();
        if (currentStep instanceof PickleStepTestStep) {
            String stepId = ((PickleStepTestStep) currentStep).getStep().getId();
            CucumberSoftAssertions.setCurrentStepId(stepId);
        }
    }
}

3. 事件监听器:动态修改步骤状态

实现ConcurrentEventListener,监听步骤结束事件,将有断言错误的步骤标记为失败:

import io.cucumber.plugin.ConcurrentEventListener;
import io.cucumber.plugin.event.EventPublisher;
import io.cucumber.plugin.event.PickleStepTestStep;
import io.cucumber.plugin.event.TestStepFinished;
import io.cucumber.plugin.event.TestStepStarted;
import io.cucumber.core.runtime.ScenarioImpl;
import io.cucumber.java.Scenario;

public class StepStatusUpdater implements ConcurrentEventListener {
    private final ThreadLocal<Scenario> currentScenario = new ThreadLocal<>();
    private final ThreadLocal<CucumberSoftAssertions> softAssertions = ThreadLocal.withInitial(CucumberSoftAssertions::new);

    @Override
    public void setEventPublisher(EventPublisher publisher) {
        publisher.registerHandlerFor(TestStepStarted.class, this::handleTestStepStarted);
        publisher.registerHandlerFor(TestStepFinished.class, this::handleTestStepFinished);
    }

    private void handleTestStepStarted(TestStepStarted event) {
        currentScenario.set(new ScenarioImpl(event.getTestCase(), event.getTestRun()));
    }

    private void handleTestStepFinished(TestStepFinished event) {
        if (event.getTestStep() instanceof PickleStepTestStep) {
            PickleStepTestStep step = (PickleStepTestStep) event.getTestStep();
            String stepId = step.getStep().getId();
            CucumberSoftAssertions assertions = softAssertions.get();
            
            if (assertions.getStepErrors().containsKey(stepId)) {
                ScenarioImpl scenario = (ScenarioImpl) currentScenario.get();
                // 标记当前步骤为失败,传入第一个断言错误作为失败原因
                scenario.getTestCaseState().fail(assertions.getStepErrors().get(stepId).get(0));
            }
            CucumberSoftAssertions.clearCurrentStepId();
        }
    }

    public CucumberSoftAssertions getCurrentSoftAssertions() {
        return softAssertions.get();
    }
}

4. 步骤定义与收尾钩子

在步骤中使用自定义软断言,并在场景结束时触发断言汇总:

// 步骤定义类
import io.cucumber.java.en.Then;
public class StepDefinitions {
    private final CucumberSoftAssertions softAssertions = new CucumberSoftAssertions();

    @Then("校验数值符合预期")
    public void validateNumbers() {
        softAssertions.assertThat(10).isGreaterThan(20); // 故意失败的断言
        softAssertions.assertThat(5).isEqualTo(5); // 成功的断言
    }
}

// 场景收尾钩子
import io.cucumber.java.After;
import io.cucumber.java.Scenario;

public class AfterHook {
    private final StepStatusUpdater stepStatusUpdater = new StepStatusUpdater();

    @After
    public void afterScenario(Scenario scenario) {
        stepStatusUpdater.getCurrentSoftAssertions().assertAll();
    }
}

5. 注册监听器插件

在Cucumber配置中注册StepStatusUpdater,比如在cucumber.properties中添加:

cucumber.plugin=com.yourpackage.StepStatusUpdater,pretty,html:target/cucumber-reports.html

关键说明

  • 用ThreadLocal存储步骤ID和软断言实例,保证多线程并发测试的独立性
  • 通过TestCaseState.fail()方法合法修改步骤状态,无需修改原始事件或事后编辑报告
  • 自定义软断言类保留了AssertJ的原有功能,同时实现了错误与步骤的关联

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 13:57:32