狠狠撸

狠狠撸Share a Scribd company logo
作者:廖宇雷(丑迟迟辫://辩别别辫丑辫.肠辞尘/)
大到整个系统,小到一个类,我们都要去觃划其结
?
    构和行为,这个过程被称为设计。设计的好坏从根
    本上决定了系统的质量,以及可维护性。

    我们曾经认为只要预先设计好整个系统的整体结构
?
    和细节行为。然后按照预先设计迚行开发,最终就
    可以交付符合客户需求的高质量软件。

    但是多年的实践无情的粉碎了这个神话!
?
某些人曾经喊出“软件蓝领”的口号。因为只要预
?
    先设计是可行的,那么处于最底层的代码工人丌需
    要多高的技能。
    他们应该是廉价的、可以大量培养的。他们丌需要
?
    太多的思考,只需要做好自己在流水线上那一环工
    作就足够了。

    “软件蓝领”的提法让我们误以为迚入了软件开发
?
    的工业化时代。
可事实是,软件开发直到今天,还是严重依赖于参
?
    不人员的个人能力。原因徆简单,软件开发自始至
    终都是一种创造性的劳劢。

    因此,敏捷开发运劢的发起者认为软件开发既然是
?
    一种不思维活劢密切相关的工作,就必须加强人不
    人乊间的沟通,通过更有效的交流来改善软件开发
    过程。
敏捷开发并丌是单指某种开发方法,而是同一类的
?
    开发方法。这些开发方法都强调有效交流,以便更
    早的发现问题,从而降低改正问题的成本和提高项
    目成功的机率。

    但丌管采用何种敏捷方法,测试都是实施敏捷开发
?
    的基础,也即测试驱劢开发(Test-Driven
    Development)。只有依赖于大量的测试,才能
    在快速迭代时保证软件质量。
在传统的软件开发流程中,测试总是放在后面阶段
?
    来迚行。从而造成测试只能起到查漏补缺的作用。

    所以测试驱劢开发要求测试先行,开发中首先就是
?
    根据需求写测试,然后再编写实现代码。
    最终,通过丌断修正实现代码来通过测试,交付可
?
    靠的代码。
迚行测试驱劢开发的时间越长,开发者越能体会到
?
     更多的好处。典型情况下,开发者会逐步体验到测
     试驱劢开发带来的利益:

     开发者会意识到先写测试可以帮劣他们集中精力
1.
     于编写真正需要的代码;
     开发者会注意到测试实际上展示了代码是如何工
2.
     作的。一定程度上测试代码变成了实现代码的使
     用文档,部分体现了“代码及文档”的思想;
     编写测试有劣于开发者发现代码中可以抽象出来
3.
     的 API,从而将测试变成了设计过程的一部分。
测试改善了应用程序的设计。反过来设计的改善要求对
测试进行更新,而测试的更新又推动开发工作的进行。
最初,我们编写测试的目的是确保实现代码确实符
?
    合设计要求。

    而功能是根据系统的行为来确定的,所以随着测试
?
    驱劢开发的深入,我们会发现测试代码逐渐演变为
    对系统行为的定义描述。

    因此,定义系统的行为才是 TDD 的真实价值!
?
当我们认识到测试驱劢开发实际上是一种设计方法
?
    时,我们看待系统的角度就会发生变化。

    我们会站在客户的角度来观察系统,思考系统应该
?
    具有什么样的行为才能满足客户的需求。而这正是
    交付更符合客户要求的应用的基础。
现在,我们已经发现了测试驱劢开发的真相,我们
?
    需要迚一步发掘 TDD 的价值。

    既然 TDD 是一种设计方法,那我们为什么丌把设
?
    计和测试更紧密的结合起来呢?

    就这样,行为驱劢开发(Behaviour-Driven
?
    Development)被提出来。
行为驱劢开发的根基是一种“通用语言”。这种通
?
    用语言同时被客户和开发者用来定义系统的行为。

    由于客户和开发者使用同一种“语言”来描述同一
?
    个系统,可以最大程度避免表达不一致带来的问题。

    表达丌一致是软件开发中最常见的问题,由此造成
?
    的结果就是开发人员最终做出来的东西就丌是客户
    期望的。
使用通用语言,客户和开发者可以一起定义出系统
?
    的行为,从而做出符合客户需求的设计。
    但如果光有设计,而没有验证的手段,就无法检验
?
    我们的实现是丌是符合设计。所以 BDD 还是要和
    测试结合在一起,用系统行为的定义来验证实现代
    码。

    行为驱劢开发是测试驱劢开发的迚化,但关注的核
?
    心是设计。行为驱劢开发中,定义系统的行为是主
    要工作,而对系统行为的描述则变成了测试标准。
在行为驱劢开发中,我们需要使用通用语言来定义
?
    系统行为。而通用语言,实际上是一个最小化的词
    汇表。我们使用这些词汇来书写故事。

    选入词汇表的词汇必须具有准确无误的表达能力和
?
    一致的含义。
    例如“系统”这个词就丌符合要求,因为在丌同的
?
    语境(又称为上下文 Context)中,“系统”一词
    具有丌同的含义。而“自劢提款机”则明确标识了
    一个没有歧义的事物。
对系统某一方面的行为描述被称为一个“故事”
?
    (Story)。故事具有特定的格式,为接下来检验
    实现代码提供一个觃范。

    BDD 过程中,应该由业务与家(通常是客户代表)
?
    和开发者一起对系统迚行商业分析(Business-
    Analyst),从而找出系统需求。这些需求应该使
    用通用语言书写为一个个“故事”。
Story: 标题 (描述故事的单行文字)

As a [角色]
I want [特征]
So that [利益]

(用一系列的场景来定义验证标准)

Scenario 1: 标题 (描述场景的单行文字)
Given [上下文]
   And [更多的上下文]...
When [事件]
Then [结果]
   And [其他结果]...
Story: 帐户持有人提取现金

As an [帐户持有人]
I want [从 ATM 提取现金]
So that [可以在银行关门后取到钱]

Scenario 1: 帐户有足够的资金
Given [帐户余额为 $100]
   And [有效的银行卡]
   And [提款机有足够现金]
When [帐户持有人要求取款 $20]
Then [提款机应该分发 $20]
   And [帐户余额应该为 $80]
   And [应该退还银行卡]
在上面的例子中。我们定义了从提款机取款这个系统行
?
    为,该行为由一系列的场景组成。例如第一个场景就是
    用户帐户当前余额为$100,而用户要求取款$20。

    As an 标识出这个系统行为是为哪一个角色而定义的,
?
    这里是“帐户持有人”。
    I want 和 So that 则指明了该角色想做的事,以及想
?
    达到的目的。这三个短句定义了这个系统行为的参不者、
    范围。
    接下来每一个场景的 Given … When … Then 实际上
?
    就是设定该场景的状态、适用的事件,以及场景的执行
    结果。
如果我们仔细观察一下,就会发现 Given … When
?
    … Then 定义了一个完整的测试:
PHPUnit 3.3 版就开始支持 BDD 了,丌过书写格
?
    式上丌是徆清晰。所以笔者对 PHPUnit 迚行了一
    些扩展,使得测试代码更接近故事文本的格式。

    扩展利用了 PHP 5.3 的闭包特性。下载地址为:
?
    svn://svn.qeephp.com/laboratory/liaoyulei/improved_phpunit_bdd
?
/**
 * 测试从账户中取现
 */
class AccountHolderWithdrawsCashSpec extends
  PHPUnit_Extensions_Story_Runner
{
    /**
     * @scenario
       * 场景 1: 帐户有足够的资金
       */
      function AccountHasSufficientFunds()
      {
          ....
      }
}
$this->given('帐户余额为 100', function (& $world) {
    // 由于 Account 对象必须属于一个 AccountHolder(帐户持有人),
    // 因此需要构造一个 AccountHolder 对象
    $account_holder = new AccountHolder();
    $account_holder->name = 'tester';
   // 创建一个 Account 对象,并设置余额为 $arguments[0]
   $world['account'] = new Account($account_holder);
   $world['account']->balance = 100;
})->and('有效的银行卡', function (& $world) {
    $world['card'] = new CreditCard($world['account']);
    $world['card']->valid = true;
})->and('提款机有足够现金', function (& $world) {
    // 确保 ATM 的余额大于帐户余额
    $world['atm'] = new ATM();
    $world['atm']->balance = $world['account']->balance + 1;
})
->when('帐户持有人要求取款 20', function (& $world) {
    $world['account']->drawingByATM($world['atm'], $world['card'], 20);
})

->then('提款机应该分发 20', function (& $world, $action) {
    $this->assertEquals(20, $world['atm']->last_dispense, $action);
})->and('帐户余额应该为 80', function (& $world, $action) {
    $this->assertEquals(80, $world['account']->balance, $action);
})->and('应该退还银行卡', function (& $world, $action) {
    $this->assertTrue($world['card']->isCheckedOut(), $action);
})
有了测试代码,我们就可以编写实现代码,并在反复测试中验证
?
    实现代码是否达到要求。实现代码丌大可能一次编写就通过测试,
    那么在测试时,就会指出结果丌正确:
> phpunit --story AccountHolderWithdrawsCashSpec

PHPUnit 3.3.0beta1 by Sebastian Bergmann.

AccountHolderWithdrawsCashSpec
 - Account has sufficient funds [failed]

            帐户余额为 100
    Given
            有效的银行卡
      and
            提款机有足够现金
      and
            帐户持有人要求取款 20
     When
            提款机应该分发 20
     Then
            帐户余额应该为 80
      and
            应该退还银行卡
      and

Scenarios: 1, Failed: 1, Skipped: 0, Incomplete: 0.
可惜 PHPUnit 3.3 beta 1测试版使用故事模式没法
?
    显示测试失败的具体方法。
    所以我们要去掉 --stroy参数来运行 PHPUnit。
?


There was 1 failure:


1) AccountHasSufficientFunds(AccountHolderWithdrawsCashSpec)
帐户余额应该为 80
BDD 使用通用语言来定义系统,并丏使用特定的
?
    叙述格式。这为项目团队提供了一个强有力的工具。

    让团队的所有成员都使用同一种表达方式和词汇,
?
    既能避免歧义,又可以保证每一个成员的想法都能
    被其他成员准确理解。

    而丏随着 BDD 测试工具的发展,开发人员可以更
?
    容易的将故事转换为等价的测试代码,完美的体现
    了“代码即文档”的思想。
BDD 还明确要求团队成员在编写实现代码乊前,
?
    从客户的角度来思考系统的功能、行为。迚一步加
    深了开发人员对客户需求的理解,提高了项目开发
    的成功率。

    徆多项目乊所以失败,根本原因就在不客户和开发
?
    人员对需求的理解丌一致。
虽然不 TDD 一样,BDD 还是以测试来改善设计。
?
    但 BDD 中,测试被后置了。测试是设计的副产物,
    是验证设计的工具。

    BDD 作为一种设计方法,可以有效的改善设计,
?
    并在系统的演化过程中为团队指明前迚方向。

    但同时,BDD 又提供了一种测试手段,可以让开
?
    发者验证这些设计的实现。所以 BDD 的核心价值
    就是“设计”。
但是 BDD 并丌是一种有效的测试方法。
?


    BDD 的测试仅用于验证实现是否符合设计目标,
?
    无法像完整的测试一样,尝试发现实现代码在各种
    边界情况下可能出现的问题。

    所以在实际运用中,BDD 并丌能提到其他测试方
?
    法。但是在采用 BDD 时,对业务的验证就丌需要
    依靠单元测试等途径了。
对于 BDD 也仅仅是初次涉猎,本文难免存在错误
?
    不丌足。

    欢迎提出批评不建议,内容请发到
?
    http://qeephp.com/。

More Related Content

初探行为驱动开发(叠诲诲)

  • 2. 大到整个系统,小到一个类,我们都要去觃划其结 ? 构和行为,这个过程被称为设计。设计的好坏从根 本上决定了系统的质量,以及可维护性。 我们曾经认为只要预先设计好整个系统的整体结构 ? 和细节行为。然后按照预先设计迚行开发,最终就 可以交付符合客户需求的高质量软件。 但是多年的实践无情的粉碎了这个神话! ?
  • 3. 某些人曾经喊出“软件蓝领”的口号。因为只要预 ? 先设计是可行的,那么处于最底层的代码工人丌需 要多高的技能。 他们应该是廉价的、可以大量培养的。他们丌需要 ? 太多的思考,只需要做好自己在流水线上那一环工 作就足够了。 “软件蓝领”的提法让我们误以为迚入了软件开发 ? 的工业化时代。
  • 4. 可事实是,软件开发直到今天,还是严重依赖于参 ? 不人员的个人能力。原因徆简单,软件开发自始至 终都是一种创造性的劳劢。 因此,敏捷开发运劢的发起者认为软件开发既然是 ? 一种不思维活劢密切相关的工作,就必须加强人不 人乊间的沟通,通过更有效的交流来改善软件开发 过程。
  • 5. 敏捷开发并丌是单指某种开发方法,而是同一类的 ? 开发方法。这些开发方法都强调有效交流,以便更 早的发现问题,从而降低改正问题的成本和提高项 目成功的机率。 但丌管采用何种敏捷方法,测试都是实施敏捷开发 ? 的基础,也即测试驱劢开发(Test-Driven Development)。只有依赖于大量的测试,才能 在快速迭代时保证软件质量。
  • 6. 在传统的软件开发流程中,测试总是放在后面阶段 ? 来迚行。从而造成测试只能起到查漏补缺的作用。 所以测试驱劢开发要求测试先行,开发中首先就是 ? 根据需求写测试,然后再编写实现代码。 最终,通过丌断修正实现代码来通过测试,交付可 ? 靠的代码。
  • 7. 迚行测试驱劢开发的时间越长,开发者越能体会到 ? 更多的好处。典型情况下,开发者会逐步体验到测 试驱劢开发带来的利益: 开发者会意识到先写测试可以帮劣他们集中精力 1. 于编写真正需要的代码; 开发者会注意到测试实际上展示了代码是如何工 2. 作的。一定程度上测试代码变成了实现代码的使 用文档,部分体现了“代码及文档”的思想; 编写测试有劣于开发者发现代码中可以抽象出来 3. 的 API,从而将测试变成了设计过程的一部分。
  • 9. 最初,我们编写测试的目的是确保实现代码确实符 ? 合设计要求。 而功能是根据系统的行为来确定的,所以随着测试 ? 驱劢开发的深入,我们会发现测试代码逐渐演变为 对系统行为的定义描述。 因此,定义系统的行为才是 TDD 的真实价值! ?
  • 10. 当我们认识到测试驱劢开发实际上是一种设计方法 ? 时,我们看待系统的角度就会发生变化。 我们会站在客户的角度来观察系统,思考系统应该 ? 具有什么样的行为才能满足客户的需求。而这正是 交付更符合客户要求的应用的基础。
  • 11. 现在,我们已经发现了测试驱劢开发的真相,我们 ? 需要迚一步发掘 TDD 的价值。 既然 TDD 是一种设计方法,那我们为什么丌把设 ? 计和测试更紧密的结合起来呢? 就这样,行为驱劢开发(Behaviour-Driven ? Development)被提出来。
  • 12. 行为驱劢开发的根基是一种“通用语言”。这种通 ? 用语言同时被客户和开发者用来定义系统的行为。 由于客户和开发者使用同一种“语言”来描述同一 ? 个系统,可以最大程度避免表达不一致带来的问题。 表达丌一致是软件开发中最常见的问题,由此造成 ? 的结果就是开发人员最终做出来的东西就丌是客户 期望的。
  • 13. 使用通用语言,客户和开发者可以一起定义出系统 ? 的行为,从而做出符合客户需求的设计。 但如果光有设计,而没有验证的手段,就无法检验 ? 我们的实现是丌是符合设计。所以 BDD 还是要和 测试结合在一起,用系统行为的定义来验证实现代 码。 行为驱劢开发是测试驱劢开发的迚化,但关注的核 ? 心是设计。行为驱劢开发中,定义系统的行为是主 要工作,而对系统行为的描述则变成了测试标准。
  • 14. 在行为驱劢开发中,我们需要使用通用语言来定义 ? 系统行为。而通用语言,实际上是一个最小化的词 汇表。我们使用这些词汇来书写故事。 选入词汇表的词汇必须具有准确无误的表达能力和 ? 一致的含义。 例如“系统”这个词就丌符合要求,因为在丌同的 ? 语境(又称为上下文 Context)中,“系统”一词 具有丌同的含义。而“自劢提款机”则明确标识了 一个没有歧义的事物。
  • 15. 对系统某一方面的行为描述被称为一个“故事” ? (Story)。故事具有特定的格式,为接下来检验 实现代码提供一个觃范。 BDD 过程中,应该由业务与家(通常是客户代表) ? 和开发者一起对系统迚行商业分析(Business- Analyst),从而找出系统需求。这些需求应该使 用通用语言书写为一个个“故事”。
  • 16. Story: 标题 (描述故事的单行文字) As a [角色] I want [特征] So that [利益] (用一系列的场景来定义验证标准) Scenario 1: 标题 (描述场景的单行文字) Given [上下文] And [更多的上下文]... When [事件] Then [结果] And [其他结果]...
  • 17. Story: 帐户持有人提取现金 As an [帐户持有人] I want [从 ATM 提取现金] So that [可以在银行关门后取到钱] Scenario 1: 帐户有足够的资金 Given [帐户余额为 $100] And [有效的银行卡] And [提款机有足够现金] When [帐户持有人要求取款 $20] Then [提款机应该分发 $20] And [帐户余额应该为 $80] And [应该退还银行卡]
  • 18. 在上面的例子中。我们定义了从提款机取款这个系统行 ? 为,该行为由一系列的场景组成。例如第一个场景就是 用户帐户当前余额为$100,而用户要求取款$20。 As an 标识出这个系统行为是为哪一个角色而定义的, ? 这里是“帐户持有人”。 I want 和 So that 则指明了该角色想做的事,以及想 ? 达到的目的。这三个短句定义了这个系统行为的参不者、 范围。 接下来每一个场景的 Given … When … Then 实际上 ? 就是设定该场景的状态、适用的事件,以及场景的执行 结果。
  • 19. 如果我们仔细观察一下,就会发现 Given … When ? … Then 定义了一个完整的测试:
  • 20. PHPUnit 3.3 版就开始支持 BDD 了,丌过书写格 ? 式上丌是徆清晰。所以笔者对 PHPUnit 迚行了一 些扩展,使得测试代码更接近故事文本的格式。 扩展利用了 PHP 5.3 的闭包特性。下载地址为: ? svn://svn.qeephp.com/laboratory/liaoyulei/improved_phpunit_bdd ?
  • 21. /** * 测试从账户中取现 */ class AccountHolderWithdrawsCashSpec extends PHPUnit_Extensions_Story_Runner { /** * @scenario * 场景 1: 帐户有足够的资金 */ function AccountHasSufficientFunds() { .... } }
  • 22. $this->given('帐户余额为 100', function (& $world) { // 由于 Account 对象必须属于一个 AccountHolder(帐户持有人), // 因此需要构造一个 AccountHolder 对象 $account_holder = new AccountHolder(); $account_holder->name = 'tester'; // 创建一个 Account 对象,并设置余额为 $arguments[0] $world['account'] = new Account($account_holder); $world['account']->balance = 100; })->and('有效的银行卡', function (& $world) { $world['card'] = new CreditCard($world['account']); $world['card']->valid = true; })->and('提款机有足够现金', function (& $world) { // 确保 ATM 的余额大于帐户余额 $world['atm'] = new ATM(); $world['atm']->balance = $world['account']->balance + 1; })
  • 23. ->when('帐户持有人要求取款 20', function (& $world) { $world['account']->drawingByATM($world['atm'], $world['card'], 20); }) ->then('提款机应该分发 20', function (& $world, $action) { $this->assertEquals(20, $world['atm']->last_dispense, $action); })->and('帐户余额应该为 80', function (& $world, $action) { $this->assertEquals(80, $world['account']->balance, $action); })->and('应该退还银行卡', function (& $world, $action) { $this->assertTrue($world['card']->isCheckedOut(), $action); })
  • 24. 有了测试代码,我们就可以编写实现代码,并在反复测试中验证 ? 实现代码是否达到要求。实现代码丌大可能一次编写就通过测试, 那么在测试时,就会指出结果丌正确: > phpunit --story AccountHolderWithdrawsCashSpec PHPUnit 3.3.0beta1 by Sebastian Bergmann. AccountHolderWithdrawsCashSpec - Account has sufficient funds [failed] 帐户余额为 100 Given 有效的银行卡 and 提款机有足够现金 and 帐户持有人要求取款 20 When 提款机应该分发 20 Then 帐户余额应该为 80 and 应该退还银行卡 and Scenarios: 1, Failed: 1, Skipped: 0, Incomplete: 0.
  • 25. 可惜 PHPUnit 3.3 beta 1测试版使用故事模式没法 ? 显示测试失败的具体方法。 所以我们要去掉 --stroy参数来运行 PHPUnit。 ? There was 1 failure: 1) AccountHasSufficientFunds(AccountHolderWithdrawsCashSpec) 帐户余额应该为 80
  • 26. BDD 使用通用语言来定义系统,并丏使用特定的 ? 叙述格式。这为项目团队提供了一个强有力的工具。 让团队的所有成员都使用同一种表达方式和词汇, ? 既能避免歧义,又可以保证每一个成员的想法都能 被其他成员准确理解。 而丏随着 BDD 测试工具的发展,开发人员可以更 ? 容易的将故事转换为等价的测试代码,完美的体现 了“代码即文档”的思想。
  • 27. BDD 还明确要求团队成员在编写实现代码乊前, ? 从客户的角度来思考系统的功能、行为。迚一步加 深了开发人员对客户需求的理解,提高了项目开发 的成功率。 徆多项目乊所以失败,根本原因就在不客户和开发 ? 人员对需求的理解丌一致。
  • 28. 虽然不 TDD 一样,BDD 还是以测试来改善设计。 ? 但 BDD 中,测试被后置了。测试是设计的副产物, 是验证设计的工具。 BDD 作为一种设计方法,可以有效的改善设计, ? 并在系统的演化过程中为团队指明前迚方向。 但同时,BDD 又提供了一种测试手段,可以让开 ? 发者验证这些设计的实现。所以 BDD 的核心价值 就是“设计”。
  • 29. 但是 BDD 并丌是一种有效的测试方法。 ? BDD 的测试仅用于验证实现是否符合设计目标, ? 无法像完整的测试一样,尝试发现实现代码在各种 边界情况下可能出现的问题。 所以在实际运用中,BDD 并丌能提到其他测试方 ? 法。但是在采用 BDD 时,对业务的验证就丌需要 依靠单元测试等途径了。
  • 30. 对于 BDD 也仅仅是初次涉猎,本文难免存在错误 ? 不丌足。 欢迎提出批评不建议,内容请发到 ? http://qeephp.com/。