BDD on Rust: 用Cucumber和Specs编写活文档

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

为什么 Rust 上的 BDD 不是枯燥的测试

\n\n

想象一下:您正在用 Rust 编写一个高负载服务。代码安全、快速,但当产品交付给客户时,客户却说:“我以为按钮应该是另一种工作方式。” 熟悉吗?这是代码与期望之间的典型鸿沟。正是在这里,BDD(行为驱动开发) 方法论应运而生,它将业务需求转化为经理和 Rust 编译器都能理解的语言。

\n\n

在本文中,我们将探讨如何在实际中实践Rust 上的 BDD。您将学习如何用人类语言(Gherkin)编写场景,使用 cucumber 和 specs 自动化它们,以及为什么这能让您的代码不仅可靠,而且对整个团队透明。没有废话——只有活代码和真实示例。

\n\n

什么是 BDD,为什么 Rust 是它的理想选择

\n\n

行为驱动开发是 TDD 的演进。如果说 TDD 关注的是“如何测试代码”,那么 BDD 回答的是“我们希望看到什么行为”。您用 Gherkin 语言(Given-When-Then)编写场景,这些场景既是测试,也是文档。

\n\n

Rust 是一种热爱严谨性和可预测性的语言。Rust 中的 BDD 提供了:

\n
    \n
  • 自文档化代码——任何开发人员阅读场景就能理解业务逻辑。
  • \n
  • 类型级别的安全性——编译器检查您的步骤是否符合结构。
  • \n
  • 并行执行——得益于 async 和多线程,Rust 中的 BDD 测试运行迅速。
  • \n
\n\n

什么时候 Rust 上的 BDD 特别有用?

\n
    \n
  • 具有明确契约的 API 服务(REST、GraphQL)。
  • \n
  • 具有业务规则的系统(金融、物流、游戏)。
  • \n
  • 微服务,其中组件间的集成至关重要。
  • \n
\n\n

Rust 中的 BDD 工具:Cucumber 和 Specs

\n\n

Rust 生态系统提供了两种主要方法:

\n\n

1. cucumber——经典 BDD 框架

\n

允许您用 Gherkin 编写场景(.feature 文件)并将它们与 Rust 代码绑定。支持 async、自定义宏和报告。

\n\n

2. specs——轻量且快速的替代方案

\n

不需要 Gherkin 文件——场景直接用 Rust 通过宏编写。如果您不想引入单独的解析器,这是理想选择。

\n\n

我们将重点介绍 cucumber,因为它提供了最大的灵活性并符合 BDD 标准。

\n\n

编写第一个 Rust BDD 场景

\n\n

让我们创建一个简单的折扣计算器。业务规则:如果购买金额超过 1000 卢布,折扣为 10%。否则为 0%。

\n\n

步骤 1. 安装依赖

\n

在 Cargo.toml 中添加:

\n
[dev-dependencies]\ncucumber = "0.20"\ntokio = { version = "1", features = ["full"] }\nasync-trait = "0.1"\n
\n\n

步骤 2. 创建 feature 文件

\n

文件 tests/features/discount.feature:

\n
Feature: 折扣计算\n  As a 顾客\n  I want 在大额购买时获得折扣\n  So that 省钱\n\n  Scenario: 购买金额超过 1000 卢布\n    Given 购买金额等于 "1500"\n    When 我请求计算折扣\n    Then 折扣金额应为 "150"\n\n  Scenario: 购买金额低于 1000 卢布\n    Given 购买金额等于 "500"\n    When 我请求计算折扣\n    Then 折扣金额应为 "0"\n
\n\n

步骤 3. 用 Rust 编写步骤

\n

创建 tests/steps/discount_steps.rs:

\n
use cucumber::{given, then, when, World};\nuse async_trait::async_trait;\n\n#[derive(Debug, Default)]\nstruct DiscountWorld {\n    purchase_amount: f64,\n    discount: f64,\n}\n\n#[async_trait(?Send)]\nimpl World for DiscountWorld {\n    // 为简化起见,空实现\n    async fn new() -> Self {\n        Self::d                

博客

书籍推荐