
本文介绍在 Serenity BDD(基于 Cucumber)中,如何通过合理使用标签与占位步骤定义,使 @manual 场景正常纳入报告展示,同时避免因缺失步骤实现而抛出 UndefinedStepException 导致构建失败。
本文介绍在 serenity bdd(基于 cucumber)中,如何通过合理使用标签与占位步骤定义,使 `@manual` 场景正常纳入报告展示,同时避免因缺失步骤实现而抛出 `undefinedstepexception` 导致构建失败。
在 Serenity BDD 项目中,团队常需将部分测试用例标记为 手动执行(如探索性测试、环境受限场景或第三方系统验证),并希望它们出现在最终的 Serenity 报告中以体现测试覆盖范围和人工验证状态。典型做法是为 Feature 文件中的 Scenario 添加 @manual 标签及相关元数据(如 @manual-result:passed、@manual-test-evidence 等)。然而,Cucumber 的核心设计原则是:每个 Gherkin 步骤(Given/When/Then)必须有且仅有一个对应的 Java 方法实现;否则,运行时将立即抛出 io.cucumber.junit.UndefinedStepException,导致整个测试套件构建失败。
例如,以下 Feature 片段看似合理:
@JIRA_BOND_007 @manual @manual-result:passed Scenario: Dont want Manual steps to cause step definition failure Given John Ferguson Smart releases a new version of serenity bdd When tester marks a test as manual Then runner should not fail because of error "io.cucumber.junit.UndefinedStepException:"
但只要 @manual 场景中存在未实现的步骤(如 When tester marks a test as manual),Cucumber 就会强制报错——即使你期望它“跳过执行”。
⚠️ 关键事实(来自 Serenity 创始人 John Ferguson Smart 的官方说明):
“Cucumber does not allow steps to not have a step definition (they will result in a build failure). There isn't an easy work-around to this.”
这意味着:无法通过配置或标签让 Cucumber 主动忽略某类场景的步骤匹配逻辑。Serenity 本身不改变 Cucumber 的这一底层约束。
✅ 正确解法:为 @manual 场景提供空实现的占位步骤定义,确保语法合法、构建通过,同时明确传达“此步骤不自动执行”的语义。推荐两种实践方式:
方式一:统一空实现(推荐)
在任意 Step Definition 类中添加通用占位方法,覆盖常见动词模式:
import io.cucumber.java.PendingException;
import io.cucumber.java.en.*;
public class ManualStepPlaceholder {
@Given(".*")
@When(".*")
@Then(".*")
public void skipManualStep() {
// 显式标记为手动执行,不触发实际逻辑
// Serenity 仍会记录该步骤为 PASSED(因无异常)
}
}✅ 优点:简洁、全局生效,无需为每个 manual 场景重复编写;Serenity 报告中步骤状态为 PASSED(因方法成功返回),符合预期。
⚠️ 注意:正则 .* 会匹配所有步骤,因此仅应在仅用于 manual 场景的独立测试运行配置中启用(例如通过 --tags "@manual" 单独执行),避免干扰自动化场景。
方式二:结构化占位(更清晰、更安全)
为 manual 场景显式编写最小化、语义明确的步骤定义:
public class ManualTestSteps {
@Given("^{string} releases a new version of serenity bdd$")
public void placeholderGiven(String person) {
// Intentionally left blank — manual verification only
}
@When("^tester marks a test as manual$")
public void placeholderWhen() {
// No automation; verified manually per test evidence
}
@Then("^runner should not fail because of error \"([^\"]*)\"$")
public void placeholderThen(String expectedError) {
// Confirmed: no UndefinedStepException thrown
}
}✅ 优点:步骤绑定精准,避免误匹配;代码可读性强,便于后续维护或审计。
? 提示:可在方法体内添加 System.out.println("[MANUAL] " + ...) 或自定义日志,辅助调试。
最终建议与注意事项
- 不要依赖 @PendingException 或 throw new PendingException():这会导致步骤状态为 PENDING,Serenity 报告中显示为黄色警告,且不符合“已人工验证通过”的业务语义。
- 务必配合 Serenity 的 @manual 标签使用:Serenity 能识别该标签并自动在 HTML 报告中标记为「Manual Test」,同时保留 @manual-result 等自定义属性的可视化。
- CI/CD 流程中建议分离执行:通过 Maven/Gradle 配置,对 @manual 场景使用专用的测试运行器(如 mvn verify -Dcucumber.filter.tags="@manual"),避免与自动化测试混跑增加噪音。
- 长期演进方向:若 manual 测试比例高,建议考虑将纯人工用例迁出 .feature 文件,改用 Serenity 的 @ManualTest 注解 + JUnit5 方法级描述,实现更干净的职责分离。
通过上述方式,你既能满足合规性与报告完整性要求,又严格遵守 Cucumber 的契约约束——这是当前生态下最稳健、最被广泛验证的实践路径。










