
1. 为什么我们需要Cucumber从“鸡同鸭讲”到“共同语言”如果你是一名Java开发者或者正在学习Java你可能已经习惯了在IDE里写下一行行逻辑严密的代码用JUnit或TestNG写单元测试来验证某个方法返回的结果是否正确。这很好这是开发者的本职工作。但不知道你有没有遇到过这样的场景产品经理拿着一份满是业务术语的需求文档来找你你们讨论了半小时你以为你完全理解了一周后你交付了功能测试同学根据另一份测试用例开始验证然后反馈说“这个逻辑不对和产品需求有出入”。于是你、产品、测试又坐在一起对着那份可能已经存在歧义的文字文档再次陷入“你说的A和我理解的A是不是同一个A”的争论中。这种沟通成本在敏捷开发、快速迭代的今天是巨大的浪费。而Cucumber就是为了解决这个问题而生的。它不是另一个单元测试框架它的核心价值在于定义了一种各方都能理解的“共同语言”——Gherkin。这种语言看起来就像简单的英语句子或其他自然语言描述的是系统的行为而不是具体的代码实现。比如对于一个登录功能用Gherkin可以这样描述功能用户登录 为了确保账户安全 作为网站用户 我希望能够用我的用户名和密码登录系统 场景使用正确的凭据登录成功 假如 我在网站的登录页面 当 我输入用户名 testUser 并且 我输入密码 securePass123 并且 我点击“登录”按钮 那么 我应该被重定向到用户主页 并且 我应该看到欢迎信息 “欢迎回来testUser”看这段描述没有一行代码但产品经理、业务分析师、测试工程师和开发者都能看懂并且对“登录成功”这个行为达成一致的理解。Cucumber的作用就是将这些人类可读的句子我们称之为“步骤定义”与后端真正的Java代码我们称之为“步骤定义实现”连接起来。当Cucumber运行时它会逐行解析这个.feature文件找到对应的Java代码去执行并报告每一步是通过还是失败。所以学习Cucumber对于Java开发者而言不仅仅是学习一个新工具更是掌握一种提升团队协作效率、确保软件交付质量与业务需求对齐的实践方法——行为驱动开发BDD。它让你的测试代码成为了活的、可执行的文档。注意很多人误以为Cucumber是给测试人员用的。其实不然它是给整个团队用的。开发者编写步骤定义的实现代码是BDD流程中的关键一环。没有开发者的深度参与Cucumber就只是形式化的文档而已。2. 环境搭建与项目初始化从零开始的十分钟准备理论说再多不如动手跑一遍。我们从一个干净的起点开始确保你能在本地成功运行第一个Cucumber场景。这里我选择最通用的构建工具Maven来管理项目IDE使用IntelliJ IDEAEclipse步骤类似。2.1 创建Maven项目与核心依赖首先打开你的IDE创建一个新的Maven项目groupId和artifactId按你的习惯来比如com.example和cucumber-demo。接下来打开项目根目录下的pom.xml文件这是项目的“购物清单”。我们需要添加Cucumber的依赖。对于Java核心依赖主要有三个cucumber-java 提供了编写步骤定义Step Definitions的注解如Given,When,Then和支持类。cucumber-junit 提供了与JUnit测试运行器集成的支持让我们能用RunWith(Cucumber.class)来运行特性文件。junit 基础的JUnit框架。将以下依赖添加到你的pom.xml的dependencies部分dependencies dependency groupIdio.cucumber/groupId artifactIdcucumber-java/artifactId version7.15.0/version !-- 请使用当时最新稳定版 -- scopetest/scope /dependency dependency groupIdio.cucumber/groupId artifactIdcucumber-junit/artifactId version7.15.0/version scopetest/scope /dependency dependency groupIdjunit/groupId artifactIdjunit/artifactId version4.13.2/version !-- Cucumber JUnit模块目前仍主要兼容JUnit 4 -- scopetest/scope /dependency /dependencies为什么是JUnit 4而不是JUnit 5虽然Cucumber社区正在积极支持JUnit 5通过cucumber-junit-platform-engine但JUnit 4的支持最为成熟稳定插件生态也更好对于入门来说能避免很多兼容性上的坑。等熟练掌握后迁移到JUnit 5是水到渠成的事情。添加依赖后IDE通常会提示你导入变更Import Changes点击确认Maven会自动从中央仓库下载这些库文件。2.2 项目目录结构规划一个清晰的目录结构能让你的测试代码井井有条。在src/test/java下创建你的Java包例如com.example.stepdefs。在src/test/resources下创建一个目录用来存放我们的特性文件通常就叫features。这是Cucumber默认会去查找的地方。最终你的项目结构应该大致如下cucumber-demo ├── pom.xml └── src ├── main │ └── java │ └── com.example │ └── (你的应用代码) └── test ├── java │ └── com.example │ └── stepdefs │ └── (步骤定义Java类) └── resources └── features └── (你的 .feature 文件)这个结构符合Maven约定也便于Cucumber自动发现测试资源。2.3 编写第一个特性文件现在在src/test/resources/features目录下创建一个新文件命名为login.feature。我们将实现上面提到的登录场景。将以下内容复制到login.feature中# language: zh-CN 功能用户登录 为了确保账户安全 作为网站用户 我希望能够用我的用户名和密码登录系统 场景大纲用户登录功能验证 假如 用户在登录页面 当 用户输入用户名 用户名 并且 用户输入密码 密码 并且 用户点击登录按钮 那么 用户应看到提示信息“预期结果” 例子 | 用户名 | 密码 | 预期结果 | | testUser | correctPass | 登录成功跳转至主页 | | wrongUser| anyPass | 用户名或密码错误 | | testUser | wrongPass | 用户名或密码错误 |这里我做了两处重要改动让教程更实用第一行# language: zh-CN显式指定了这个特性文件使用中文关键词如“假如”、“当”。如果你的IDE安装了Cucumber插件这能提供更好的语法高亮和提示。我将“场景”升级为了“场景大纲”并添加了“例子”表格。这是Gherkin中非常强大的功能可以用一组数据来运行同一个场景多次极大地减少了重复的步骤描述。你马上就能体会到它的好处。保存文件后如果你的IDE安装了Cucumber插件如IntelliJ IDEA的“Cucumber for Java”你会看到.feature文件有了漂亮的语法着色。3. 步骤定义实现连接自然语言与Java代码的桥梁现在我们有了人类可读的需求描述.feature文件但计算机看不懂。我们需要创建“步骤定义”来告诉计算机当遇到“用户在登录页面”这句话时应该执行什么Java代码。3.1 生成步骤定义骨架最聪明的方法不是手写而是让IDE帮你生成骨架。在IntelliJ IDEA中打开login.feature文件你会看到每个步骤以“假如”、“当”、“那么”等开头的句子旁边都有一个暗淡的灯泡图标或“未定义的步骤”提示。点击它选择“Create step definition”然后选择在之前创建的com.example.stepdefs包下生成一个新的Java类比如命名为LoginStepDefinitions。IDEA会自动生成类似下面的代码package com.example.stepdefs; import io.cucumber.java.zh_cn.假如; import io.cucumber.java.zh_cn.当; import io.cucumber.java.zh_cn.那么; public class LoginStepDefinitions { 假如(用户在登录页面) public void 用户在登录页面() { // Write code here that turns the phrase above into concrete actions throw new io.cucumber.java.PendingException(); } 当(用户输入用户名 {string}) public void 用户输入用户名(String string) { // Write code here that turns the phrase above into concrete actions throw new io.cucumber.java.PendingException(); } // ... 其他步骤方法 }注意因为我们用了中文关键词IDEA聪明地导入了io.cucumber.java.zh_cn.*下的注解。如果是英文关键词Given, When, Then则会导入io.cucumber.java.en.*。3.2 实现步骤定义逻辑生成的骨架方法都抛出了PendingException表示还未实现。我们的任务就是用真正的代码替换它们。为了演示我们不会去启动一个真实的浏览器或调用远程API而是模拟一个简单的登录服务。这叫做“模拟”或“桩”在测试中非常常见可以让我们专注于业务逻辑和Cucumber本身。首先在src/main/java下模拟生产代码创建一个简单的登录服务类package com.example.service; public class LoginService { private static final String VALID_USERNAME testUser; private static final String VALID_PASSWORD correctPass; public String login(String username, String password) { if (VALID_USERNAME.equals(username) VALID_PASSWORD.equals(password)) { return 登录成功跳转至主页; } else { return 用户名或密码错误; } } }然后我们回到LoginStepDefinitions类实现步骤定义。我们需要一个地方来保存测试过程中的状态比如输入的用户名、密码以及登录的结果。我们可以使用类的成员变量。package com.example.stepdefs; import com.example.service.LoginService; import io.cucumber.java.zh_cn.假如; import io.cucumber.java.zh_cn.当; import io.cucumber.java.zh_cn.那么; import io.cucumber.java.zh_cn.并且; import static org.junit.Assert.assertEquals; public class LoginStepDefinitions { // 模拟的页面状态或测试上下文 private String username; private String password; private String actualMessage; private final LoginService loginService new LoginService(); 假如(用户在登录页面) public void 用户在登录页面() { // 这里可以初始化页面状态例如打开浏览器导航到登录页。 // 因为是模拟我们只是简单打印日志或初始化变量。 System.out.println(模拟用户打开登录页面...); // 重置状态确保场景独立 username null; password null; actualMessage null; } 当(用户输入用户名 {string}) public void 用户输入用户名(String inputUsername) { this.username inputUsername; System.out.println(模拟在用户名输入框输入: inputUsername); } 当(用户输入密码 {string}) public void 用户输入密码(String inputPassword) { this.password inputPassword; System.out.println(模拟在密码输入框输入: inputPassword); } 当(用户点击登录按钮) public void 用户点击登录按钮() { // 这里应该是触发登录动作例如点击按钮然后调用后端服务。 System.out.println(模拟点击登录按钮...); // 调用我们模拟的登录服务 actualMessage loginService.login(username, password); System.out.println(模拟收到服务响应: actualMessage); } 那么(用户应看到提示信息{string}) public void 用户应看到提示信息(String expectedMessage) { // 这是断言步骤验证实际结果是否符合预期。 System.out.println(验证期望提示信息是: expectedMessage); assertEquals(登录结果提示信息不匹配, expectedMessage, actualMessage); } }关键点解析{string}参数捕获在步骤定义的方法中当(“用户输入用户名 {string}”)里的{string}是一个捕获组。它会将特性文件中对应位置的字符串如“testUser”提取出来作为方法参数String inputUsername传入。这是Cucumber实现数据驱动测试的核心机制之一。状态共享我们使用类的成员变量 (username,password,actualMessage) 来在同一个场景的不同步骤之间共享数据。这是实现步骤间协作的标准做法。断言在那么注解的方法中我们使用JUnit的assertEquals来验证实际结果actualMessage是否等于预期结果expectedMessage。这是测试的最终验证点。中文注解我们使用了假如、当、那么、并且。并且和当在Cucumber看来是等价的只是为了在Gherkin中让句子读起来更通顺。你可以用当来匹配“并且”开头的步骤。3.3 创建JUnit测试运行器我们需要一个入口点来告诉JUnit如何运行Cucumber测试。在src/test/java下创建一个新的Java类比如叫RunCucumberTest。这个类通常放在一个独立的包或根目录下。package com.example; 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.example.stepdefs, // 指定步骤定义类所在的包 plugin {pretty, html:target/cucumber-reports.html}, // 指定报告格式 monochrome true // 让控制台输出更整洁 ) public class RunCucumberTest { // 这个类不需要有任何代码它只是一个配置载体。 }CucumberOptions参数详解features: 告诉Cucumber去哪里找.feature文件。可以指定目录或单个文件。glue: 告诉Cucumber去哪里找步骤定义Step Definitions和其他的钩子类Hooks。这里我们指定了步骤定义所在的包。plugin: 配置输出插件。“pretty”会在控制台输出彩色、格式美观的结果。“html:target/cucumber-reports.html”会生成一个HTML格式的测试报告放在target目录下这对于查看详细的测试历史结果非常有用。monochrome: 设置为true如果控制台不支持ANSI颜色可以避免出现乱码让日志更清晰。4. 运行测试与结果解析一切就绪现在可以运行测试了。在IDE中右键点击RunCucumberTest类选择 “Run ‘RunCucumberTest’”。或者你也可以在命令行中进入项目根目录执行 Maven 命令mvn clean testMaven会执行surefire插件运行所有以Test结尾的JUnit类其中就包括我们的RunCucumberTest。4.1 解读控制台输出运行成功后你会在控制台看到类似下面的彩色输出场景大纲用户登录功能验证 例子 | 用户名 | 密码 | 预期结果 | | testUser | correctPass | 登录成功跳转至主页 | 假如 用户在登录页面... # LoginStepDefinitions.用户在登录页面() 当 用户输入用户名 testUser... # LoginStepDefinitions.用户输入用户名(String) 并且 用户输入密码 correctPass... # LoginStepDefinitions.用户输入密码(String) 并且 用户点击登录按钮... # LoginStepDefinitions.用户点击登录按钮() 那么 用户应看到提示信息“登录成功跳转至主页” # LoginStepDefinitions.用户应看到提示信息(String) ✔ 例子 | 用户名 | 密码 | 预期结果 | | wrongUser| anyPass | 用户名或密码错误 | 假如 用户在登录页面... # LoginStepDefinitions.用户在登录页面() 当 用户输入用户名 wrongUser... # LoginStepDefinitions.用户输入用户名(String) 并且 用户输入密码 anyPass... # LoginStepDefinitions.用户输入密码(String) 并且 用户点击登录按钮... # LoginStepDefinitions.用户点击登录按钮() 那么 用户应看到提示信息“用户名或密码错误” # LoginStepDefinitions.用户应看到提示信息(String) ✔ 例子 | 用户名 | 密码 | 预期结果 | | testUser | wrongPass | 用户名或密码错误 | 假如 用户在登录页面... # LoginStepDefinitions.用户在登录页面() 当 用户输入用户名 testUser... # LoginStepDefinitions.用户输入用户名(String) 并且 用户输入密码 wrongPass... # LoginStepDefinitions.用户输入密码(String) 并且 用户点击登录按钮... # LoginStepDefinitions.用户点击登录按钮() 那么 用户应看到提示信息“用户名或密码错误” # LoginStepDefinitions.用户应看到提示信息(String) ✔ 3 Scenarios (3 passed) 9 Steps (9 passed) 0m0.267s太棒了三个场景来自“例子”表中的三行数据全部通过3 passed。每个步骤后面都显示了对应的步骤定义方法最后的✔符号表示该步骤执行成功。最下面的摘要清晰地统计了场景数和步骤数以及总耗时。4.2 查看HTML报告同时Cucumber在target/cucumber-reports.html生成了一个HTML报告。用浏览器打开这个文件你会看到一个更直观、信息更丰富的视图。它通常包括概览通过/失败场景的饼图或总结。特性列表每个.feature文件的执行情况。场景详情点击每个场景可以展开看到每个步骤的执行状态、耗时如果失败还会有错误堆栈信息。步骤定义列表展示了所有已定义的步骤及其被哪些特性文件使用。这个HTML报告是分享测试结果、回溯历史问题的绝佳工具。你可以配置CI/CD工具如Jenkins在每次构建后归档这个报告。5. 进阶技巧与最佳实践避开我踩过的那些坑恭喜你成功运行了第一个Cucumber测试但要让Cucumber在真实项目中发挥威力避免沦为“花瓶”还需要掌握一些进阶技巧和最佳实践。这些都是我在多个项目中实战后总结出来的经验。5.1 步骤定义的复用与设计模式随着特性文件增多步骤定义很容易变得臃肿和重复。遵循以下原则可以保持代码清晰单一职责一个步骤定义方法只做一件事。例如“输入用户名”和“输入密码”分开而不是一个“输入凭据”的方法。使用正则表达式和自定义参数类型{string}是最简单的参数捕获。你还可以使用正则表达式来匹配更复杂的模式并定义自己的参数类型转换器。// 匹配 “用户购买了 3 个苹果” 当(“用户购买了 (\\d) 个(.)“) public void 用户购买了商品(int 数量, String 商品名) { // 数量会被自动转换为int }对于复杂对象如日期、自定义类可以实现io.cucumber.java.TypeRegistryConfigurer接口来注册自定义的类型转换器。提取公共步骤到父类或工具类如果多个步骤定义类都需要访问WebDriver用于UI自动化或同一个测试数据服务可以将其提取到一个基类中或者使用依赖注入框架如PicoContainer、Spring。Cucumber本身支持简单的依赖注入。5.2 钩子方法在场景前后执行代码钩子Hooks允许你在场景Scenario或步骤Step执行的生命周期中插入代码常用于初始化和清理工作。创建一个新的类例如Hooks放在步骤定义所在的包或子包下因为glue路径会扫描子包。package com.example.stepdefs; import io.cucumber.java.After; import io.cucumber.java.Before; import io.cucumber.java.Scenario; public class Hooks { Before public void setUp(Scenario scenario) { // 在每个场景开始前执行 System.out.println( 开始执行场景: scenario.getName() ); // 这里可以初始化WebDriver、数据库连接等 } After public void tearDown(Scenario scenario) { // 在每个场景结束后执行无论成功还是失败 System.out.println( 场景执行结束: scenario.getName() “ 状态: ” scenario.getStatus() ); // 这里可以关闭WebDriver、清理测试数据、截图如果失败等 if (scenario.isFailed()) { // 例如截取屏幕截图并附加到Cucumber报告中 // byte[] screenshot ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES); // scenario.attach(screenshot, image/png, 失败截图); System.out.println(场景失败需排查); } } // 还可以有 BeforeStep, AfterStep 在每一步前后执行但慎用可能影响性能。 }Scenario对象非常有用它可以让你获取当前场景的名称、状态通过、失败、未定义等并且在After钩子中你可以附加文件如图片、文本到报告中这对于调试UI自动化测试的失败案例至关重要。5.3 数据驱动测试与场景大纲的妙用你已经体验了“场景大纲”和“例子”表格的威力。这是实现数据驱动测试最优雅的方式。但数据量大了怎么办外部数据文件对于大量、复杂的数据可以将“例子”表格的数据放在外部的CSV、Excel或JSON文件中然后在步骤定义中读取。不过Cucumber本身不直接支持从文件加载例子你需要自己在Before钩子或步骤定义中写代码读取。一个更Cucumber风格的做法是将数据文件路径作为场景参数传入。动态生成例子你甚至可以在步骤定义中通过Java代码动态生成测试数据集合但这会破坏特性文件的可读性需谨慎使用。BDD的初衷是让非技术人员也能看懂需求所以尽量保持特性文件的纯粹性。5.4 与Selenium等UI自动化工具集成Cucumber最经典的应用场景之一就是Web UI自动化测试。与Selenium集成非常直接。添加Selenium依赖到pom.xmldependency groupIdorg.seleniumhq.selenium/groupId artifactIdselenium-java/artifactId version4.15.0/version !-- 使用当时最新版 -- scopetest/scope /dependency在钩子中管理WebDriver生命周期在Before中初始化WebDriver如ChromeDriver在After中关闭它。为了能在不同步骤定义类中共享同一个Driver实例通常将其存储在一个静态的上下文类或使用ThreadLocal。public class WebDriverContext { private static final ThreadLocalWebDriver driver new ThreadLocal(); public static WebDriver getDriver() { return driver.get(); } public static void setDriver(WebDriver webDriver) { driver.set(webDriver); } public static void quitDriver() { if (getDriver() ! null) { getDriver().quit(); driver.remove(); } } } // 在 Hooks 类中 Before public void setUp() { WebDriver driver new ChromeDriver(); driver.manage().window().maximize(); WebDriverContext.setDriver(driver); } After public void tearDown() { WebDriverContext.quitDriver(); }在步骤定义中使用WebDriver当(“我导航到登录页面”) public void 我导航到登录页面() { WebDriverContext.getDriver().get(https://your-app.com/login); }5.5 标签组织与过滤测试用例当你有成百上千个场景时如何有选择地运行它们答案是标签。在特性文件中使用符号给场景或整个特性打标签smoke login 功能用户登录 ... wip 场景忘记密码功能 ... regression 场景大纲用户登录功能验证 ...然后在RunCucumberTest类的CucumberOptions中通过tags属性来过滤tags “smoke”只运行带有smoke标签的场景。tags “smoke and login”运行同时带有smoke和login标签的场景。tags “regression and not wip”运行带有regression标签但不带wip标签的场景。wip(Work In Progress) 常用于标记尚未完成的场景避免它们干扰正常的测试套件。你可以在Maven命令中覆盖这个配置实现动态过滤mvn test -Dcucumber.filter.tags“smoke”6. 常见问题与排查技巧实录即使按照教程一步步来你也可能会遇到一些问题。下面是我在帮助团队和学员入门时最常被问到的几个问题及其解决方案。6.1 步骤定义未找到问题运行测试时控制台输出显示步骤是黄色的并提示Undefined step: ...。原因Cucumber找不到匹配该步骤语句的步骤定义方法。排查检查glue路径确保CucumberOptions(glue “...” )中的包路径正确并且包含了你的步骤定义类所在的包及其子包。检查步骤文本匹配步骤定义方法上的注解字符串必须与.feature文件中的步骤文本完全匹配包括空格和标点。一个常见的错误是中文标点不匹配全角/半角。建议直接使用IDE的“跳转到步骤定义”功能来检查。检查参数占位符如果步骤中包含动态部分如{string}确保步骤定义中的占位符格式正确。{string}匹配带引号的字符串(\\d)匹配数字。重新构建项目有时IDE的索引可能滞后。尝试执行mvn clean compile test-compile或刷新IDE的项目。6.2 测试报告为空或未生成问题运行后控制台有输出但target/cucumber-reports.html文件不存在或内容为空。原因路径问题html:target/cucumber-reports.html这个路径是相对于当前工作目录的。如果你在子模块或不同目录下运行路径可能不对。可以尝试使用绝对路径或html:target/cucumber/report.html。插件配置错误确保plugin选项拼写正确。“html”是插件名冒号后面是路径。测试全部跳过或未执行如果所有场景都因为标签过滤或步骤未定义而被跳过报告可能不会生成。检查控制台输出确认有场景实际执行了。Maven清理mvn clean会删除target目录。确保是在clean之后又执行了test。6.3 场景独立性被破坏问题第一个场景通过了但第二个场景失败了而且错误看起来和第一个场景的数据有关。原因步骤定义类中的状态成员变量没有在场景之间正确重置。Cucumber默认会为每个场景创建一个新的步骤定义类实例。所以如果你把状态保存在成员变量里本身是隔离的。问题通常出在使用了静态变量静态变量属于类在所有实例间共享绝对不要在步骤定义类中使用静态变量来保存场景状态。外部共享资源未清理例如数据库连接、文件句柄、浏览器会话如果未正确关闭在场景间残留。必须在After钩子中彻底清理。解决始终坚持“场景独立”原则。初始化工作在Before做清理工作在After做。状态尽量保存在步骤定义类的非静态成员变量中。6.4 匹配歧义问题运行时报错io.cucumber.core.exception.AmbiguousStepDefinitionsException提示一个步骤匹配到了多个步骤定义。原因有两个或多个步骤定义方法可以匹配同一条Gherkin步骤。例如一个步骤定义是当 我点击按钮另一个是当 我点击“登录”按钮而你的步骤是当 我点击“登录”按钮它可能同时匹配两者如果第一个使用了通配符。解决检查所有步骤定义确保它们的表达式是精确的。尽量避免使用过于宽泛的正则表达式。使用更具体的步骤描述。例如用当 我点击“提交”按钮代替当 我点击按钮。如果确实需要通用步骤可以考虑使用Cucumber的步骤参数化将按钮名称作为参数传入当 我点击“{string}”按钮。6.5 与Spring框架集成时的依赖注入问题问题在步骤定义类中Autowired注入Spring Bean失败报NullPointerException。原因Cucumber的步骤定义类默认不是由Spring容器管理的。你需要显式地配置Cucumber使用Spring的依赖注入。解决添加cucumber-spring依赖。dependency groupIdio.cucumber/groupId artifactIdcucumber-spring/artifactId version7.15.0/version scopetest/scope /dependency创建一个Spring配置类并用CucumberContextConfiguration注解标记。同时确保你的步骤定义类在Spring的组件扫描路径下或被ContextConfiguration指定。import io.cucumber.spring.CucumberContextConfiguration; import org.springframework.boot.test.context.SpringBootTest; CucumberContextConfiguration SpringBootTest(classes YourMainApplication.class) // 或者使用 ContextConfiguration public class CucumberSpringConfiguration { }现在你的步骤定义类就可以使用Autowired了。Cucumber会在每个场景开始前为这个场景创建一个Spring应用上下文并将Bean注入到步骤定义实例中。掌握以上这些核心概念、实操步骤和避坑指南你已经具备了在Java项目中运用Cucumber进行BDD实践和自动化测试的扎实基础。记住Cucumber的核心价值在于沟通与协作代码只是实现这份共同约定的手段。从一个小功能开始和你的团队成员一起编写和维护这些“活的文档”你会逐渐体会到它带来的长远收益。