Automation Panda 2017-10-24
本文是对 Cucumber-JVM for Java 的简明而全面的概述。它既是一篇入门指南,也是一份参考手册。如果你还不熟悉 BDD(行为驱动开发),请先学习相关知识再使用 Cucumber-JVM。
简介
Cucumber 是一个用于行为驱动开发(BDD)的开源软件测试自动化框架。它使用一种业务可读、领域特定的语言——Gherkin,来描述功能行为,并将其转化为自动化测试用例。
Cucumber 项目始于 2008 年,由 Aslak Hellesøy 发布了第一个 Ruby 版本。
Cucumber-JVM 是 Cucumber 官方为 Java 提供的移植版本。在 Cucumber-JVM 中,每一个 Gherkin 步骤(step)都会“绑定”到一个执行该步骤的 Java 方法(称为 step definition)。这种绑定通过注解和正则表达式实现。
Cucumber-JVM 能与各种测试工具良好集成。只要是 Java 能做到的事情,Cucumber-JVM 都能处理。它特别适合用于黑盒测试、高于单元级别的功能测试。
更新(2018年7月29日):从 3.0.0 版本起,Cucumber-JVM 不再支持除 Java 以外的 JVM 语言(如 Groovy、Scala、Clojure 和 Gosu)。详情请参阅官方 Cucumber 博客文章《Cucumber-JVM is dropping support of JVM Languages》。此外,Gherkin 解析器现在改用 Go 语言编写,而非 Java。
示例项目
GitHub 上提供了两个示例项目,用于配合本文学习:
- cucumber-jvm-java-example:使用传统的注解风格编写步骤定义
- cucumber-jvm-java8-example:使用 Java 8 的 Lambda 表达式风格编写步骤定义
这些项目使用了 Java、Apache Maven、Selenium WebDriver 和 AssertJ。README 文件中还包含练习题。
先决技能
要成功使用 Cucumber-JVM for Java,你需要掌握以下技能:
- BDD 和 Gherkin(推荐阅读 BDD 101)
- 中级 Java 编程能力
- 测试自动化经验(如 JUnit、TestNG)
- 构建工具使用经验(如 Ant、Maven、Gradle)
必备工具
测试机器必须安装 Java 开发工具包(JDK),以便构建和运行 Cucumber-JVM 测试。同时建议安装所需的构建工具(如 Apache Maven),它会通过依赖管理自动下载 Cucumber-JVM 包。
推荐使用以下 IDE 进行 Cucumber-JVM 自动化开发:
- JetBrains IntelliJ IDEA(需安装 Cucumber for Java 插件)
- Eclipse(需安装 Cucumber JVM Eclipse Plugin)
强烈建议使用 Git 等软件配置管理(SCM)工具进行版本控制。
版本说明
- Cucumber-JVM 2.0 于 2017 年 8 月发布,新项目应使用此版本或更高版本。
- 所有新版 Cucumber-JVM 包均位于 Maven Group ID
io.cucumber下。 - 旧版 Cucumber-JVM 1.x 的包位于
info.cukes下。
构建管理
Apache Maven 是 Cucumber-JVM 项目的首选构建工具。所有 Cucumber-JVM 包都托管在 Maven Central Repository 上,Maven 可在构建过程中自动运行 Cucumber 测试。
项目应遵循 Maven 标准目录结构(Standard Directory Layout)。示例项目均使用 Maven。虽然 Gradle 也可使用,但需要额外配置。
每个 Maven 项目都有一个 POM 文件用于配置。POM 应包含适当的 Cucumber-JVM 依赖项。由于 Cucumber-JVM 是测试框架,其依赖应使用 test 作用域。
提示:不同 JVM 语言、依赖注入框架和底层测试运行器(如 JUnit)都有对应的独立包。请访问 Maven Central - io.cucumber 获取最新包和版本信息。
项目结构
Cucumber-JVM 测试自动化采用与其他 BDD 框架相同的分层架构:
BDD Automation Layers.png
- 上层:侧重于规范描述(如 Gherkin 特性文件)
- 下层:侧重于技术实现(如步骤定义、页面对象)
Cucumber-JVM 测试可以放在产品代码项目中,也可以单独成项目。无论哪种方式,都应遵循 Maven 标准目录结构:测试代码应放在 src/test 目录下。
Gherkin 特性文件(Feature Files)
Gherkin 特性文件是包含行为场景的文本文件,扩展名为 .feature。在 Maven 项目中,它们应放在 src/test/resources 目录下(因为它们不是 Java 源文件),并按合理的包结构组织。
建议参考其他 BDD 文档学习如何编写高质量的 Gherkin。
步骤定义类(Step Definition Classes)
步骤定义类是包含实现 Gherkin 步骤方法的 Java 类。它们和普通 Java 类一样,可以包含变量、构造函数和方法。步骤通过正则表达式 + 注解绑定到方法。
- 特性文件中的任何步骤都可以调用项目中任意步骤定义类中的方法。
- 在 Maven 项目中,步骤定义类应放在
src/test/java下的包中,类名建议以Steps结尾。
基础示例(传统注解风格)
以下来自 cucumber-jvm-java-example 项目,使用 cucumber-java 包的传统注解方式。每个方法应声明抛出 Throwable,以便异常能被 Cucumber-JVM 捕获。
package com.automationpanda.example.stepdefs;
import com.automationpanda.example.pages.GooglePage;
import cucumber.api.java.After;
import cucumber.api.java.Before;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import static org.assertj.core.api.Assertions.assertThat;
public class GoogleSearchSteps {
private WebDriver driver;
private GooglePage googlePage;
@Before(value = "@web", order = 1)
public void initWebDriver() throws Throwable {
driver = new ChromeDriver();
}
@Before(value = "@google", order = 10)
public void initGooglePage() throws Throwable {
googlePage = new GooglePage(driver);
}
@Given("^a web browser is on the Google page$")
public void aWebBrowserIsOnTheGooglePage() throws Throwable {
googlePage.navigateToHomePage();
}
@When("^the search phrase \"([^\"]*)\" is entered$")
public void theSearchPhraseIsEntered(String phrase) throws Throwable {
googlePage.enterSearchPhrase(phrase);
}
@Then("^results for \"([^\"]*)\" are shown$")
public void resultsForAreShown(String phrase) throws Throwable {
assertThat(googlePage.pageTitleContains(phrase)).isTrue();
}
@After(value = "@web")
public void disposeWebDriver() throws Throwable {
driver.quit();
}
}
Java 8 Lambda 风格(更简洁)
在 Java 8 中,可使用 Lambda 表达式编写步骤定义(需 cucumber-java8 包):
package com.automationpanda.example.stepdefs;
import com.automationpanda.example.pages.GooglePage;
import cucumber.api.Scenario;
import cucumber.api.java8.En;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import static org.assertj.core.api.Assertions.assertThat;
public class GoogleSearchSteps implements En {
private WebDriver driver;
private GooglePage googlePage;
// 注意:使用 WebDriver 的钩子超时应设为 0
public GoogleSearchSteps() {
Before(new String[]{"@web"}, 0, 1, (Scenario scenario) -> {
driver = new ChromeDriver();
});
Before(new String[]{"@google"}, 0, 10, (Scenario scenario) -> {
googlePage = new GooglePage(driver);
});
Given("^a web browser is on the Google page$", () -> {
googlePage.navigateToHomePage();
});
When("^the search phrase \"([^\"]*)\" is entered$", (String phrase) -> {
googlePage.enterSearchPhrase(phrase);
});
Then("^results for \"([^\"]*)\" are shown$", (String phrase) -> {
assertThat(googlePage.pageTitleContains(phrase)).isTrue();
});
After(new String[]{"@web"}, (Scenario scenario) -> {
driver.quit();
});
}
}
提示:IDE(如 IntelliJ)支持自动生成步骤定义的 stub(桩代码)。
最佳实践:避免使用类继承。父类中的步骤绑定会导致运行时抛出
DuplicateStepDefinitionException。任何通过继承实现的关注点,都可通过其他设计模式(如组合)更好地解决。构造函数主要用于依赖注入,初始化操作应放在Before钩子中。
钩子(Hooks)
有些自动化任务(如初始化 WebDriver)不适合写在 Gherkin 中。Cucumber-JVM 提供 Before 和 After 钩子,分别在场景执行前和后运行,类似于 JUnit 的 @Before / @After。
- 钩子可指定标签(tags)和执行顺序(order)。
After钩子即使在场景失败时也会执行,非常适合做清理工作。
示例:
@Before(value = "@web", order = 1)
public void initWebDriver() throws Throwable {
driver = new ChromeDriver();
}
@Before(value = "@google", order = 10)
public void initGooglePage() throws Throwable {
googlePage = new GooglePage(driver);
}
@After(value = "@web")
public void disposeWebDriver() throws Throwable {
driver.quit();
}
注意:Cucumber-JVM 没有提供整个测试套件级别的钩子(如
@BeforeAll)。这保证了测试独立性,但也使全局初始化变得困难。
解决方案:使用单例模式 + 懒加载。详见 Cucumber-JVM Global Hook Workarounds。
依赖注入(Dependency Injection)
Cucumber-JVM 支持依赖注入(DI),用于在多个步骤定义类之间共享对象(如 WebDriver 实例)。
- 不要使用静态变量共享状态——这会破坏测试独立性和并行执行能力。
- Cucumber-JVM 支持多种 DI 框架(如 PicoContainer、Spring、Guice),每种都有对应的依赖包。
推荐:PicoContainer(最简单)
- 无需额外配置。
- 对象通过构造函数参数注入。
- 同一类型对象在所有需要它的步骤类中是同一个实例(但每个场景会创建新实例)。
示例:
public class WebDriverHolder {
private WebDriver driver;
public WebDriver getDriver() { return driver; }
public void initWebDriver() { driver = new ChromeDriver(); }
}
public class GoogleSearchSteps {
private WebDriverHolder holder;
public GoogleSearchSteps(WebDriverHolder holder) {
this.holder = holder;
}
@Before
public void initWebDriver() throws Throwable {
if (holder.getDriver() == null)
holder.initWebDriver();
}
}
提示:需要构造参数的对象,可通过 Holder 类或缓存类封装。
自动化支持类(Automation Support Classes)
支持类是 Cucumber-JVM 框架之外、用于实现自动化逻辑的辅助类。它们可以来自:
- 当前测试项目
- 私有工具包
- 开源库
步骤定义应尽量简短,复杂逻辑应委托给支持类,以提高复用性。
常用开源支持库:
- Selenium WebDriver:Web 浏览器交互
- REST Assured:REST API 测试
- AssertJ:流式断言
- SLF4J / Logback / log4j2:日志记录
- Extent Reports:测试报告
页面对象(Page Objects)、文件读取器、数据处理器等也都属于支持类。
配置文件(Configuration Files)
配置文件用于提供环境相关数据,如:
- URL、用户名、密码
- 日志/报告设置
- 数据库连接信息
建议格式:CSV、XML、JSON、Java Properties(避免 Excel 等二进制格式,不利于版本控制和 diff)。
- 配置文件应放在预定义位置,或通过环境变量/系统属性传入路径。
- 在测试套件启动时一次性加载(通过全局钩子变通方案)。
- 切勿将配置硬编码到代码中。
运行测试
1. 使用 JUnit 或 TestNG
通过 cucumber-junit 或 cucumber-testng 包,结合测试运行器类(Runner Class)执行:
package com.automationpanda.example.runners;
import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith;
@RunWith(Cucumber.class)
@CucumberOptions(
plugin = {"pretty", "html:target/cucumber", "junit:target/cucumber.xml"},
features = "src/test/resources/com/automationpanda/example/features",
glue = {"com.automationpanda.example.stepdefs"})
public class PandaCucumberTest {
}
- Maven 会自动运行
*Test.java(测试阶段)和*IT.java(集成测试阶段)。 - 使用
mvn clean清理旧结果。 - 使用 标签(tags) 避免重复执行相同测试。
2. 使用命令行运行器
Cucumber-JVM 提供 CLI 工具:
java cucumber.api.cli.Main
加 --help 查看所有选项。
3. 使用 IDE
- IntelliJ IDEA(安装 Cucumber for Java 插件)
- Eclipse(安装 Cucumber JVM Eclipse Plugin)
支持:步骤跳转、桩代码生成、灵活运行选项。
Cucumber 选项(Cucumber Options)
可通过以下方式设置:
- Runner 类中的
@CucumberOptions - 命令行系统属性:
-Dcucumber.options="..."
最实用的选项是 --tags,用于动态选择要运行的场景。
Cucumber-JVM 2.0+ 的标签表达式语法(类英语布尔表达式):
@automated and @web
@web or @service
not @manual
(@web or @service) and (not @wip)
旧版本使用
~@manual, @web等复杂语法,现已弃用。
并行执行(Parallel Execution)
并行执行可大幅缩短总测试时间,但需注意:
- 需要多台机器或线程
- 需要避免测试间资源冲突(如数据库、文件)
- 测试需设计为无状态、独立
从 Cucumber-JVM 4.0.0 起,原生支持并行执行。
旧版本通常借助 Maven 插件(如 Cucumber-JVM Parallel Plugin 或 Trivago 的 Cucable)实现。