什么是BDD,为什么它不只是又一个时髦的概念?
\n\n您是否曾经编写过在编译器看来完美运行,但完全不符合业务需求的代码?是否遇到过经理看着完成的功能说:“嗯,我以为会是另一种样子”?如果是这样——欢迎来到BDD(行为驱动开发)的世界。这不仅仅是一种方法论,更是连接“我想要按钮是绿色的”和“这里有一个测试,如果按钮不是绿色就会失败”之间的桥梁。
\n\n与传统的TDD(测试驱动开发)不同,TDD关注的是将测试视为函数验证,而BDD则聚焦于从用户角度出发的系统行为。在C++的背景下,这一点尤为宝贵:语言复杂,项目庞大,因与客户沟通误解而重写逻辑代价高昂。C++中的BDD有助于一箭双雕:获得连项目经理都能理解的文档,以及一套不会破坏业务逻辑的自动化测试。
\n\n在本文中,我们将探讨如何将行为驱动开发应用到真实的C++项目中。不掺水分,附带代码和具体技巧。
\n\nBDD基础:Gherkin语言及其在C++中的作用
\n\nBDD的核心是使用Gherkin语言编写的场景。它们看起来大致如下:
\n\nFeature: 账户间转账\n Scenario: 余额充足时成功转账\n Given 用户拥有余额为1000元的账户A\n And 用户拥有余额为0元的账户B\n When 用户从账户A向账户B转账500元\n Then 账户A的余额变为500元\n And 账户B的余额变为500元\n\n是的,这不是C++。但这正是其魔力所在:场景用人类语言编写,然后通过步骤(steps)“绑定”到实际的C++代码。每一行Given、When、Then都变成一个函数,调用您的类并检查结果。
对于C++,有几个框架能够处理Gherkin。最流行的有:
\n\n- \n
- 带有BDD风格的Catch2——轻量级,内置于Catch2,无需外部解析器。 \n
- Cucumber-CPP——Cucumber在C++中的完整实现,直接解析.feature文件。 \n
- Boost.Test——也支持类似BDD的语法,但灵活性稍差。 \n
我个人建议从Catch2开始:它已经存在于90%的C++项目中,并且进行BDD无需安装任何额外内容。如果项目规模较大且需要与业务分析师集成,则选择Cucumber-CPP。
\n\n使用Catch2进行BDD:直接在代码中编写场景
\n\nCatch2提供了宏SCENARIO、GIVEN、WHEN、THEN以及AND_WHEN/AND_THEN。这不是纯粹的Gherkin,但风格非常接近。让我们为一个简单的银行账户编写测试。
首先——账户类:
\n\n// account.h\n#pragma once\n#include <string>\n#include <stdexcept>\n\nclass Account {\npublic:\n explicit Account(std::string owner, double initial_balance = 0.0)\n : owner_(std::move(owner)), balance_(initial_balance) {}\n\n void deposit(double amount) {\n if (amount <= 0) throw std::invalid_argument("金额必须为正数");\n balance_ += amount;\n }\n\n void withdraw(double amount) {\n if (amount > balance_) throw std::runtime_error("余额不足");\n if (amount <= 0) throw std::invalid_argument("金额必须为正数");\n balance_ -= amount;\n }\n\n double balance() const { return balance_; }\n const std::string& owner() const { return owner_; }\n\nprivate:\n std::string owner_;\n double balance_ = 0.0;\n};\n\n现在——使用Catch2的BDD测试:
\n\n// tes