BDD与Cucumber实践指南:用Gherkin语法编写可执行需求,驱动团队高效协作

发布时间:2026/7/29 2:11:38
BDD与Cucumber实践指南:用Gherkin语法编写可执行需求,驱动团队高效协作 1. 项目概述从“鸡同鸭讲”到“共同语言”的工程实践在软件开发的日常里我们常常陷入一种困境产品经理拿着一份充满业务术语的PRD产品需求文档兴致勃勃地描述着“用户应该能一键完成心愿单合并”而开发工程师的脑海里瞬间浮现的是API接口设计、数据库事务和幂等性处理。测试同学则在一旁默默盘算着边界用例和异常流。大家说的好像是一件事但理解上却隔着几座山。这种沟通的鸿沟轻则导致需求反复修改重则直接交付一个“这不是我想要的”产品。BDD行为驱动设计和Cucumber就是为了填平这道鸿沟而生的工程实践与工具组合。简单来说BDD不是一种具体的技术而是一种协作哲学和开发流程。它鼓励项目中的不同角色——业务、开发、测试——使用一种统一的、基于业务场景的语言来定义需求、驱动开发和验证结果。而Cucumber则是实现BDD理念的一个流行工具它允许我们用近乎自然语言的格式Gherkin语法来编写可执行的规格说明也就是我们常说的Feature文件。这个文件就是团队的“共同语言”载体。它既是需求文档又是测试用例还能直接驱动自动化测试。本次分享我将结合多年在敏捷团队中推行BDD的经验深入拆解Cucumber Feature文件的编写心法并梳理出一套能让团队高效协作、真正落地的流程帮你把BDD从“概念”变成“生产力”。2. BDD与Cucumber核心思想解析2.1 BDD的本质从“测试后置”到“需求前置”很多人初次接触BDD和Cucumber会误以为它们只是一种高级的自动化测试框架。这个理解是片面的甚至可以说是本末倒置。BDD的核心是“设计”和“沟通”而非“测试”。它源于TDD测试驱动开发但将关注点从“代码是否正确”提升到了“软件行为是否符合业务预期”。传统的开发流程往往是线性的需求分析 - 设计 - 编码 - 测试。测试活动被置于链条末端其作用更像是“质量检验”发现问题时修复成本已经很高。BDD试图将这个流程扭转过来在编码开始之前所有相关方就坐下来用具体的业务场景示例来讨论并达成对需求的共识。这个讨论的产出物就是那些用Given-When-Then格式描述的场景。这样一来验收标准在开发前就已明确且无歧义开发过程变成了不断满足这些已达成共识的“可执行需求”的过程测试则自然成为了验证这些需求是否被满足的活动从而实现了需求、开发、测试的三位一体。2.2 Cucumber的角色可执行规格的“翻译官”与“运行器”Cucumber在其中扮演了两个关键角色。首先它是一个“翻译官”。它定义了一套名为Gherkin的领域特定语言DSL这套语法结构简单Feature, Scenario, Given, When, Then, And, But等关键词接近自然英语使得非技术人员也能轻松阅读和编写。业务分析师或产品经理可以用它来描述需求而Cucumber负责将这些描述“翻译”成可以被测试框架理解的指令。其次它是一个“运行器”。在Gherkin场景的背后我们需要用编程语言如Java、JavaScript、Ruby等编写所谓的“步骤定义”Step Definitions。这些步骤定义将Gherkin语句中的自然语言映射到具体的代码操作上。当Cucumber执行一个Feature文件时它会逐行读取场景找到对应的步骤定义并执行其中的代码从而驱动应用程序验证其行为是否符合描述。所以Cucumber自动化测试的本质是执行那些已经被团队共同认可的业务规格。2.3 Feature文件团队协作的单一可信源基于以上理解Feature文件的地位就非常清晰了。它不应该被看作是测试团队的私有财产。恰恰相反它应该成为整个项目团队包括产品、开发、测试、甚至运维关于“系统应该做什么”的单一可信源。任何对需求的讨论、澄清和变更都应首先体现在Feature文件的更新上。它的版本应该被纳入代码库统一管理其变更应该像代码一样经过评审Pull Request。当团队养成以Feature文件为中心进行沟通的习惯时很多不必要的误解和返工就会自然消失。3. Cucumber Feature文件编写深度指南3.1 Gherkin语法精要与最佳实践Gherkin语法看似简单但要写出清晰、可维护、有价值的Feature文件需要遵循一些关键原则。1. Feature功能每个.feature文件以Feature关键字开头后面跟着功能的名称和一段简短的描述。这个描述应该从业务价值的角度出发说明这个功能为谁解决了什么问题。Feature: 用户购物车管理 作为一名在线购物者 我希望能够管理我购物车中的商品 以便于我方便地调整购买意向并完成结算。最佳实践Feature的标题应是一个名词性短语描述一个具体的业务能力。描述部分使用“As a... I want... So that...”模板能有效聚焦于用户角色和业务价值避免陷入技术实现细节。2. Scenario场景与Scenario Outline场景大纲Scenario描述一个具体的业务流示例。一个Feature下通常有多个Scenario覆盖主成功流程Happy Path和各种边界条件、异常流。Scenario: 向空购物车添加商品 Given 用户已登录并进入商品详情页 When 用户点击“加入购物车”按钮 Then 购物车图标上应显示商品数量为1 And 购物车页面应包含该商品信息当多个场景结构相同仅数据不同时使用Scenario Outline配合Examples表格可以极大减少重复。Scenario Outline: 使用不同优惠券结算 Given 我的购物车中有总价为 原始总价 的商品 And 我有一张折扣为 折扣 的优惠券 When 我应用此优惠券 Then 我的订单应付金额应为 最终价格 Examples: | 原始总价 | 折扣 | 最终价格 | | 100.00 | 10% | 90.00 | | 200.00 | 减20 | 180.00 |最佳实践每个Scenario应独立且可验证避免场景间的状态依赖。优先使用Scenario Outline处理基于数据的用例使测试数据与测试逻辑分离更易于维护和扩展。3. Steps步骤Given, When, Then, And, ButGiven设置场景的初始状态。描述在事件发生前系统所处的既定条件。可以有一个或多个Given步骤。When描述用户执行的关键操作或系统触发的事件。这是场景的“触发器”。通常一个场景只有一个When。Then验证操作的结果。描述系统应有的响应或状态变化。可以有一个或多个Then步骤来验证不同的方面。And/But用于连接同一类型的多个步骤使语句更流畅。And表示并列But表示转折。步骤编写心法业务语言而非技术语言步骤应该描述“做什么”和“看到什么”而不是“怎么实现”。避免出现“点击ID为‘submit-btn’的按钮”、“查询数据库users表”这样的技术细节。应该写“点击提交按钮”、“用户已注册”。保持原子性一个步骤应只做一件事。如果一个Then步骤验证了太多东西如“Then我应该看到成功提示购物车被清空并且收到确认邮件”就应该拆分成多个步骤这样在测试失败时能更快定位问题。使用变量抽象对于会变化的数据使用变量。例如Then 我应该看到“商品名”比写死商品名更好。变量在Scenario Outline的Examples中或在步骤定义中通过正则表达式捕获。3.2 从模糊需求到清晰场景的实例拆解假设我们收到一个需求“用户可以对商品进行收藏”。这是一个非常模糊的需求。通过与产品经理和开发同学一起进行BDD式的“实例化讨论”我们可以将其具体化为多个清晰的场景。原始模糊需求“用户可以对商品进行收藏。”通过BDD讨论后产出的Feature文件部分内容Feature: 商品收藏功能 作为一名潜在买家 我希望能够收藏我感兴趣的商品 以便于我日后快速找到并考虑购买它们。 Scenario: 登录用户收藏一个商品 Given 我已登录到我的账户 And 我正在浏览商品“夏季新款T恤”的详情页 When 我点击“收藏”按钮 Then 按钮状态应变为“已收藏” And 该商品应出现在“我的收藏”列表中 Scenario: 未登录用户尝试收藏商品 Given 我未登录 And 我正在浏览商品“夏季新款T恤”的详情页 When 我点击“收藏”按钮 Then 系统应提示我“请先登录” And 页面应跳转到登录页 Scenario: 从收藏列表中移除商品 Given 我已登录 And 我的收藏列表中已有商品“夏季新款T恤” When 我在“我的收藏”页面点击该商品旁的“取消收藏”按钮 Then 该商品应从收藏列表中消失 And 在商品详情页收藏按钮状态应恢复为“收藏” Scenario Outline: 收藏列表的显示与排序 Given 我已登录 And 我的收藏列表中有以下商品 | 商品名 | 收藏时间 | | 商品A | 2023-10-01 10:00 | | 商品B | 2023-10-05 14:30 | | 商品C | 2023-10-03 09:15 | When 我查看“我的收藏”页面 Then 商品应按 排序方式 顺序显示 Examples: | 排序方式 | | 按收藏时间倒序 | | 按商品名称升序 |通过这样的拆解模糊的需求变成了一个个可讨论、可开发、可测试的具体例子。团队对“收藏”这个功能的边界登录态、状态切换、列表管理、排序达成了共识。3.3 常见陷阱与避坑指南步骤过于臃肿包含UI细节这是最常见的错误。步骤中如果包含了具体的ID、CSS选择器一旦前端UI改动所有相关步骤定义都需要修改维护成本剧增。错误示例When I click the element with id “#add-to-cart-btn”正确做法在步骤定义层将业务语言映射到具体的UI操作。步骤中写When I add the item to the cart在步骤定义的代码里再去查找和点击那个具体的按钮。这样UI变了只需改一处代码。Scenario之间存在隐式依赖例如Scenario 2依赖于Scenario 1创建的用户或数据。这会导致测试无法独立运行且顺序敏感。避坑方法每个Scenario都必须以Given步骤开始明确地构建自己所需的所有测试上下文。充分利用后台任务或测试数据工厂来创建干净的数据确保场景隔离。验证点Then过于笼统或缺失只验证了操作成功但没有验证业务结果。例如只验证了HTTP状态码是200但没有验证数据库里确实新增了一条记录或者页面上的关键数据是否正确。避坑方法Then步骤应该从用户视角和系统状态两个维度进行断言。既要验证用户界面反馈如提示信息、页面跳转也要验证后端数据状态可通过API或直接查询数据库但需谨慎避免测试过于脆弱。滥用Background背景Background用于定义一组适用于该Feature文件下所有Scenario的通用Given步骤。但如果Background过于复杂会降低每个Scenario的可读性因为读者需要记住Background做了什么。最佳实践只将真正通用且必要的步骤放在Background中例如“Given 用户已登录”。如果大部分Scenario需要一组复杂的预设数据考虑使用场景大纲Scenario Outline或封装在步骤定义内部的数据准备方法。4. 基于Feature文件的团队协作流程设计编写出好的Feature文件只是第一步更重要的是将其融入团队的日常开发流程使其真正成为协作的枢纽。下面是一个经过实践验证的、以Feature文件为核心的敏捷协作流程。4.1 “三友会”Three Amigos需求澄清会这是BDD流程的启动器。在迭代计划会议或接到一个新需求卡片后由产品负责人或业务分析师、开发工程师和测试工程师即“三友”共同参加一个简短例如30-60分钟的会议。目标不是做详细的技术设计而是就需求的业务范围、验收标准达成一致并产出最初的Feature文件草稿。输入用户故事卡片如“作为用户我想收藏商品以便以后购买”。过程三方从各自视角提问、举例。产品讲解业务价值开发询问技术边界测试思考各种场景。大家共同使用“Given-When-Then”的格式在白板或协作工具上罗列出主场景、异常场景和边界场景。输出一个初步的、包含核心场景的Feature文件。这个文件是三方共识的物化体现消除了大量潜在歧义。4.2 Feature文件的版本控制与评审流程将初步的Feature文件放入项目的版本控制系统如Git为其创建一个特性分支。这标志着需求进入了“可执行”状态。创建Pull Request (PR)由会议发起人通常是测试或产品创建PR将Feature文件草稿提交。团队评审团队所有成员而不仅仅是“三友”都被邀请评审这个PR。评审焦点是业务正确性场景是否准确反映了需求有无遗漏清晰度与无歧义步骤描述是否所有角色都能看懂可测试性步骤是否足够具体以便实现自动化合并与基线化评审通过后将Feature文件合并到主分支如develop。此时这个Feature文件就成为了该需求的官方、唯一、最新的规格说明。任何后续的讨论和变更都必须基于此文件。4.3 开发与测试的并行工作流传统的“先开发后测试”在这里变成了“基于共同规格的并行工作”。开发侧开发人员基于已达成共识的Feature文件开始实现功能。他们甚至可以优先实现步骤定义的空壳让测试先失败然后填充业务逻辑使其逐步通过测试。这是一种BDD与TDD结合的优秀实践。测试侧测试人员几乎同步地开始实现步骤定义背后的自动化测试代码。由于场景是明确的他们可以专注于如何用代码最优雅、最稳定地实现“Given”的设景和“Then”的断言。他们也会根据场景补充更多的技术性测试数据。持续集成CI反馈每当有代码提交CI系统如Jenkins, GitLab CI会自动运行Cucumber测试。测试结果通过/失败实时反馈给团队。一个失败的测试可能意味着新代码破坏了原有功能回归也可能意味着场景描述需要更新需求变更。这个快速反馈环是保障质量的核心。4.4 迭代中的维护与演进需求在迭代中变更是常态。BDD流程优雅地处理了这种变更。变更发起任何需求变更首先反映在Feature文件的更新上。可能是添加新场景也可能是修改现有场景的步骤。再次评审对Feature文件的修改同样需要提交PR并经过团队评审确保所有人理解变更内容。同步调整开发根据更新的场景调整实现代码测试同步更新步骤定义和自动化测试。CI会立刻运行新的测试集验证变更是否被正确实现。活文档最终你的Cucumber Feature文件集合就是一份永远与系统实际行为保持同步的、可执行的、活的文档。新成员 onboarding 时阅读这些Feature文件是了解系统功能最快的方式。5. 步骤定义Step Definitions的实现策略与技巧Feature文件是“说什么”步骤定义就是“怎么做”。它是连接业务语言和系统实现的桥梁。编写健壮、可复用的步骤定义是BDD自动化成功的关键。5.1 步骤定义的映射与参数化步骤定义的本质是使用正则表达式或黄瓜表达式Cucumber Expressions来匹配Feature文件中的步骤文本并执行一段代码。// Java Cucumber-JVM 示例 public class ShoppingCartSteps { private WebDriver driver; private ShoppingCartPage cartPage; Given(用户已登录并进入商品详情页) public void userIsLoggedInAndOnProductPage() { // 实现登录和导航到商品页的代码 driver.get(/login); // ... 登录操作 driver.get(/product/123); } When(用户点击“加入购物车”按钮) public void userClicksAddToCartButton() { cartPage new ShoppingCartPage(driver); cartPage.clickAddToCart(); } Then(购物车图标上应显示商品数量为{int}) public void cartIconShouldShowItemCount(int expectedCount) { int actualCount cartPage.getCartItemCount(); Assert.assertEquals(expectedCount, actualCount); } }技巧使用黄瓜表达式{int}、{string}、{float}等可以更简洁地捕获参数。对于复杂的参数如表格可以使用DataTable类型接收。5.2 上下文共享与状态管理一个场景的多个步骤Given When Then通常需要在同一个“会话”或“状态”下执行。如何在不同步骤定义方法间共享状态如WebDriver实例、API客户端、测试数据依赖注入DI框架这是最推荐的方式。Cucumber支持与Spring (Java)、PicoContainer等DI容器集成。你可以定义一个“World”或“TestContext”类将其注入到所有步骤定义类中。// 使用Spring的示例 SpringBootTest CucumberContextConfiguration public class SpringIntegrationTest { // 测试配置 } Component Scope(cucumber-glue) // 每个场景一个实例 public class TestContext { public WebDriver driver; public String currentUserId; // ... 其他共享状态 } Component public class CommonSteps { Autowired private TestContext context; Given(用户已登录) public void userIsLoggedIn() { context.driver new ChromeDriver(); // ... 登录逻辑将userId存入context.currentUserId } }静态变量/ThreadLocal简单场景下可用但在并行运行测试时容易出现问题不推荐用于复杂项目。封装页面对象/API客户端将与UI交互或API调用的细节封装成独立的类如PageObject, API Client在步骤定义中调用它们的方法。状态由这些对象内部管理或通过返回值传递。5.3 数据准备与清理的优雅方案测试数据管理是自动化测试的基石。Given步骤中的数据准备尽量在Given步骤中通过调用业务API或使用测试数据工厂来创建数据而不是直接操作数据库或UI。这样更接近用户行为也更稳定。Given(我的购物车中有总价为 {float} 的商品) public void myCartHasItemsWithTotalPrice(float totalPrice) { // 调用“添加商品到购物车”的API直到购物车总价满足条件 // 或者使用一个预置了特定商品组合的测试用户 testDataFactory.setupCartWithTotal(context.currentUserId, totalPrice); }数据清理After钩子每个场景执行后必须清理它产生的数据避免影响后续测试。在Cucumber中使用After注解的方法。After public void tearDown(Scenario scenario) { if (context.driver ! null) { context.driver.quit(); } // 调用API清理测试用户创建的数据如清空购物车、删除测试订单等 testDataCleaner.cleanUpDataForUser(context.currentUserId); }使用测试数据库或容器为自动化测试准备一个独立的数据库每次测试套件运行前将其重置到已知状态如通过运行迁移脚本。使用Docker等技术可以轻松实现这一点。5.4 断言与报告让失败一目了然Then步骤中的断言是验证的终点。使用清晰的断言信息断言失败时错误信息应能直接帮助定位问题。例如Assert.assertEquals(“购物车商品数量不符”, expectedCount, actualCount);。多维度断言一个Then步骤可以包含多个断言但最好将其分组并确保它们是逻辑相关的。如果一个失败后续的仍会执行这有助于收集更多失败信息。利用Cucumber报告Cucumber能生成丰富的HTML报告清晰地展示哪些Feature、哪些Scenario通过了或失败了以及失败步骤的截图和错误堆栈。将此报告集成到CI流水线中并作为构建产物保存方便回溯。失败时自动截图在After钩子中判断场景状态如果失败则对当前页面截图并嵌入到报告中这对于调试UI测试至关重要。6. 将BDD-Cucumber集成到CI/CD流水线BDD的价值在持续集成和持续交付CI/CD中能得到最大程度的发挥。它不再是“跑一下”的测试而是质量关卡和发布依据。6.1 流水线阶段设计一个典型的集成BDD的CI/CD流水线可能包含以下阶段代码提交与构建开发者提交代码包括功能代码和可能的Feature文件/步骤定义更新触发流水线。首先进行代码编译和静态检查。单元测试运行快速的单元测试。BDD验收测试这是核心阶段。流水线执行Cucumber测试套件。根据测试规模可以分层执行核心冒烟测试挑选最关键、最核心的业务场景如用户登录、下单主流程组成一个快速测试集优先运行快速反馈基本功能是否完好。全量回归测试运行所有Cucumber测试。这个阶段可能耗时较长可以考虑并行化。报告生成与归档无论测试成功与否都生成详细的Cucumber HTML报告。将报告归档并提供链接到构建结果页面。质量门禁设置质量关卡。例如只有当核心冒烟测试100%通过且全量回归测试通过率在95%以上时才允许构建产物进入后续的部署阶段。部署与更高级别测试通过质量门禁后将应用部署到类生产环境Staging进行手动探索性测试、性能测试或端到端用户旅程测试。6.2 测试策略与执行优化测试分层与标签化使用Cucumber的Tag功能对场景进行分类。例如smoke冒烟、regression回归、wip工作中暂不执行。在CI配置中通过标签选择器来运行不同分层的测试。# 只运行冒烟测试 mvn test -Dcucumber.filter.tagssmoke # 运行除WIP外的所有测试 mvn test -Dcucumber.filter.tagsnot wip并行执行如果测试套件很大执行时间是瓶颈。可以利用Cucumber的并行运行特性如通过cucumber-jvm-parallel-plugin或CI工具本身的并行能力将Feature文件分发到多个执行器上同时运行大幅缩短反馈时间。环境管理CI中的测试环境应该是稳定、可重复的。使用基础设施即代码IaC工具如Terraform和容器化Docker来快速创建和销毁测试环境确保每次测试都在一个干净、一致的环境中开始。6.3 失败分析与快速修复当CI中的BDD测试失败时需要高效的排查流程查看报告首先查看Cucumber HTML报告明确是哪个Feature下的哪个Scenario失败了以及在哪一个Step失败的。区分失败类型产品缺陷真失败应用程序行为与预期不符。步骤定义正确但应用逻辑有bug。需要开发人员修复代码。测试缺陷假失败应用程序行为可能是正确的但测试本身有问题。例如步骤定义过时或错误UI改了但定位器没更新API响应格式变了但断言没改。环境/数据问题测试依赖的服务不可用测试数据被意外修改。异步等待问题网络或UI响应慢断言执行时元素还未出现需增加智能等待。需求变更Feature文件未更新这是BDD流程中期望发生的情况。测试失败恰恰提示我们代码实现与团队最新共识Feature文件不一致。此时应首先更新Feature文件然后同步更新代码和步骤定义。建立反馈闭环将失败的测试与问题跟踪系统如Jira关联。如果是产品缺陷创建Bug单如果是测试不稳定Flaky Test需要重点优化该测试提高其稳定性因为不稳定的测试会削弱团队对CI结果的信任。7. 进阶实践与常见问题攻坚7.1 处理复杂业务逻辑与状态转换对于涉及多步骤、长流程、状态复杂的业务场景直接用一个Scenario描述会非常冗长。此时可以运用一些设计模式复合步骤Composite Steps将一系列常用的步骤组合成一个更高层次的步骤。例如Given 用户已成功下单购买了一件商品背后可能包含了登录、浏览、加入购物车、填写地址、支付等多个底层步骤。在步骤定义中调用其他步骤定义的方法即可实现。但需谨慎使用避免过度抽象降低可读性。场景拆分与引用将一个长流程拆分成多个逻辑上独立的Feature或Scenario通过共享上下文如用户Token、订单号来串联。这更符合“每个场景验证一个独立业务点”的原则。使用Background设置复杂前提如果多个场景都需要一个非常复杂的初始状态可以将其封装在Background中或者更推荐的做法是封装在一个专门的Given步骤里例如Given 系统已存在一个处于待支付状态的订单在这个步骤定义内部去处理所有复杂的创建逻辑。7.2 异步操作与等待策略现代Web应用大量使用异步操作AJAX移动应用或后端API也可能有延迟。测试必须能够稳健地处理这些情况。避免静态等待Thread.sleep这是最差的做法它会让测试变慢且不可靠。使用显式等待WebDriver等工具提供了显式等待机制可以等待某个条件成立如元素可见、元素包含特定文本后再继续。Then(“页面应显示成功提示”) public void pageShouldShowSuccessMessage() { // 糟糕的做法Thread.sleep(5000); // 好的做法显式等待 WebDriverWait wait new WebDriverWait(driver, Duration.ofSeconds(10)); WebElement message wait.until(ExpectedConditions.visibilityOfElementLocated(By.id(“success-msg”))); Assert.assertTrue(message.getText().contains(“成功”)); }轮询后端状态对于触发后台任务的操作如支付处理、报告生成在Then步骤中可能需要轮询查询API或数据库直到任务达到预期状态再进行最终断言。7.3 测试数据的外部化与管理将测试数据特别是Scenario Outline中的Examples硬编码在Feature文件里在数据量大或需要频繁变更时会难以管理。从外部文件加载数据可以使用Cucumber的DataTable注解处理小表格对于大数据可以在步骤定义中从CSV、JSON或YAML文件读取数据。使用测试数据工厂创建专门的数据工厂类用于按需生成或获取测试数据。例如TestDataFactory.createUser(“buyer”)、TestDataFactory.getProduct(“standard”)。工厂内部可以决定是从内存中构造、调用API创建还是从预置的数据池中获取。环境特定配置将环境相关的配置如测试服务器的URL、不同环境的测试账号提取到配置文件如application-test.yml中通过DI注入到步骤定义里。7.4 应对“脆弱测试”Flaky Tests脆弱测试是指那些时而通过、时而失败的测试通常由竞态条件、环境不稳定、异步问题或测试数据冲突导致。它们会严重损害CI的可信度。识别与隔离首先通过CI的历史记录识别出那些经常失败的测试给它们打上flaky标签暂时从核心流水线中排除但必须限期修复。根本原因分析与修复竞态条件加强同步和等待逻辑确保操作在正确的状态后发生。环境问题确保测试环境独立、稳定。使用容器化和服务虚拟化如WireMock模拟外部依赖来提高环境可控性。测试隔离确保每个测试都使用独立的数据集避免因数据残留导致的冲突。在Before和After钩子中做好彻底的准备和清理。依赖外部服务对于第三方服务尽量使用模拟Mock或存根Stub避免网络波动或服务不可用影响测试。重试机制谨慎使用作为临时措施可以为某些已知不稳定的操作配置自动重试逻辑。但这不能替代对测试稳定性的根本性修复。