Cucumber - 入门

·850 字·4 分钟
Cucumber cucumber atdd
n3xtchen
作者
n3xtchen
Sharing Funny Tech With You

经过一段时间的学习,对 Cucumber 有了初步的了解,它所包含的理念深深的打动了我。作为 TDD 的拥护者,已经长期被需求折磨的开发来讲,希望借助 Cucumber 的推广:

  • 有效地指导我们的开发,提高效率,降低需求误解的带来的成本,避免垃圾需求的实现污染代码库;
  • 帮助需求方更好地整理思路,更准确的表述需求,以及提供更清晰的用例;

好话不多说,我们现在通过简单的流程初步认识 Cucumber

首先,假设一个案例(来自**《The Cucumber Book》**), 我们的目的为了编写一个计算器程序。

我们有个非常棒的想法(^_^),这个计算器将来有一天可能会成一个云服务,运行在每个手机,PC 以及浏览器上,帮助人们进行各种各样的计算。我们是务实的善根,因此,我们的第一个版本尽可能的简单。第一版本的程序是一个命令行工具,有 Ruby 实现;它接受输入,通过命令行输出结果。

例如,如果输入一个文件,如下:

2+2

它的输出应该是 4

类似的,如果输入的文件是:

100/2

那它的输出就应该是 50

创建一个功能(Feature) #

Cucumber 测试通过功能来分组。

我们首先要做的是创建一个目录来保存我们的程序

λ MacBook-Pro cucumber → mkdir calculator
λ MacBook-Pro cucumber → cd calculator

我们将通过 Cucumber 来指导我们的开发,因此我们先从运行 cucumber 命令开始:

λ MacBook-Pro calculator → cucumber
No such file or directory @ rb_sysopen - features. Please create a features directory to get started. (Errno::ENOENT)

因为我们没有提供任何参数给 cucumber 命令;因此,按惯例,我们使用 features 目录来保存我们的测试文件。由于不存在这个目录,就像命令提醒我们那样;那我们就按照它的指示继续:

λ MacBook-Pro calculator → mkdir features
λ MacBook-Pro calculator → cucumber
0 scenarios
0 steps
0m0.000s

现在没有报错了。讲解一些概念,每一个 Cucumber 测试就是一个 scenarios,以及每一个 scenarios 都包含多个 step。这个输出告诉我们,Cucumber 扫描了 features 目录,但是没有找到任何的 scenarios 可以执行。

根据我们的用户调查,所有的运算操作中加法占据了 67%,因此我们首先要支持他。使用你最喜欢的编辑器,创建一个叫做 features/adding.feature 的文本文件,内容如下:

 Feature: Adding
     Scenario: Add two numbers
         Given the input "2+2"
 
         When the calculator is run
         Then the output should be "4"

这个功能文件包含了我们程序的第一个场景。关键字 FeatureScenarioGivenWhenThen 是结构,其他的东西都是文档。我们称这个结构为 Gherkin

保存好你的文件,在运行一次 cucumber

λ MacBook-Pro calculator → cucumber
Feature: Adding

  Scenario: Add two numbers       # features/adding.feature:2
    Given the input "2+2"         # features/adding.feature:3
    When the calculator is run    # features/adding.feature:5
    Then the output should be "4" # features/adding.feature:6

1 scenario (1 undefined)
3 steps (3 undefined)
0m0.002s

You can implement step definitions for undefined steps with these snippets:

Given(/^the input "(.*?)"$/) do |arg1|
  pending # express the regexp above with the code you wish you had
end

When(/^the calculator is run$/) do
  pending # express the regexp above with the code you wish you had
end

Then(/^the output should be "(.*?)"$/) do |arg1|
  pending # express the regexp above with the code you wish you had
end

If you want snippets in a different programming language,
just make sure a file with the appropriate file extension
exists where cucumber looks for step definitions.

Wow,突然这么多的输出,吓的一跳吧。首先,我们可以看到 Cucumber 找到了我们的 feature,并且尝试运行它;接下来的是给出了三个 Ruby 代码片段。那我们就遵照这个指示进行下一步了,把这三个代码片段黏贴到文件中。

创建步骤(Step Definitions) #

现在,我们开始实现一些步骤,让我们的场景不再 undefined。不要在意它的含义,把它拷贝到文件中就是了;Cucumber 的惯例是把步骤定义存放在 features/step_definitions 下,现在我们要创建它:

λ MacBook-Pro calculator → mkdir features/step_definitions

现在我们在这个目录下面创建一个 Ruby 文件,命名为 calculator_step.rbCucumber 不在乎步骤文件的命名,只要他是一个 Ruby 文件,但是好的名称还是很有必要的。现在我们打开它,代码片段复制进去:

# features/step_definitions/calculator_step.rb
Given(/^the input "(.*?)"$/) do |arg1|
    pending # express the regexp above with the code you wish you had
end

When(/^the calculator is run$/) do
    pending # express the regexp above with the code you wish you had
end

Then(/^the output should be "(.*?)"$/) do |arg1|
    pending # express the regexp above with the code you wish you had
end

保存完文件,我们执行下 cucumber

λ MacBook-Pro calculator → cucumber
Feature: Adding

  Scenario: Add two numbers       # features/adding.feature:2
    Given the input "2+2"         # features/step_definitions/calculator_step.rb:1
      TODO (Cucumber::Pending)
      ./features/step_definitions/calculator_step.rb:2:in `/^the input "(.*?)"$/'
      features/adding.feature:3:in `Given the input "2+2"'
    When the calculator is run    # features/step_definitions/calculator_step.rb:5
    Then the output should be "4" # features/step_definitions/calculator_step.rb:9

1 scenario (1 pending)
3 steps (2 skipped, 1 pending)
0m0.057s

我们顺利让场景从 undefined 晋级到 pending。这是个好消息,说明 Cucumber 在执行第一个步骤,但是碰到了 pending 标记。我们现在需要使用真正的实现来替换 pending 标记。

注意 Cucumber 报道另外两个步骤被跳过;是因为当它遇到一个失败或者 pending 步骤的时候,Cucumber 将会停止执行这个场景,跳过剩下的步骤。

实现我们第一个步骤(Step Definitions) #

我们已经决定我们的第一版本是一个计算器,接受命令行的参数作为输入,因此我们的第一步就是获取一个参数;我们现在开始定义:

# features/step_definitions/calculator_step.rb
Given(/^the input "(.*?)"$/) do |input|
    @input = input
end

然后,

λ MacBook-Pro calculator → cucumber
Feature: Adding

  Scenario: Add two numbers       # features/adding.feature:2
    Given the input "2+2"         # features/step_definitions/calculator_step.rb:1
    When the calculator is run    # features/step_definitions/calculator_step.rb:5
      TODO (Cucumber::Pending)
      ./features/step_definitions/calculator_step.rb:6:in `/^the calculator is run$/'
      features/adding.feature:5:in `When the calculator is run'
    Then the output should be "4" # features/step_definitions/calculator_step.rb:9

1 scenario (1 pending)
3 steps (1 skipped, 1 pending, 1 passed)
0m0.031s

我们的第一步通过了!但是场景还是被标记为 pending,当然,是因为我们实现了一步,剩下的两个步骤还没有实现。不过我们取得了进展,不是吗?

运行我们的程序 #

现在开始实现我们的第二步:

When(/^the calculator is run$/) do
    @output = `ruby cacl.rb #{@input}`
    raise('Command failed') unless $?.success?
end

然后,

λ MacBook-Pro calculator → cucumber
Feature: Adding

  Scenario: Add two numbers       # features/adding.feature:2
    Given the input "2+2"         # features/step_definitions/calculator_step.rb:1
ruby: No such file or directory -- cacl.rb (LoadError)
    When the calculator is run    # features/step_definitions/calculator_step.rb:5
      Command failed (RuntimeError)
      ./features/step_definitions/calculator_step.rb:7:in `/^the calculator is run$/'
      features/adding.feature:5:in `When the calculator is run'
    Then the output should be "4" # features/step_definitions/calculator_step.rb:10

Failing Scenarios:
cucumber features/adding.feature:2 # Scenario: Add two numbers

1 scenario (1 failed)
3 steps (1 failed, 1 skipped, 1 passed)
0m0.103s

这个步骤失败了,是因为没有 calc.rb 这个程序可以运行。

添加断言 #

按照 cucumber 的指示,我们需要为我们的程序,创建一个 Ruby 文件。

针对 Mac/Linux 用户在命令行中输入如下代码来创建这个文件:

λ MacBook-Pro calculator → touch cacl.rb

如果是 Windows 用户,你的命令是:

​C:\> echo.>calc.rb​

然后我们再次执行 cucumber

λ MacBook-Pro calculator → cucumber
Feature: Adding

  Scenario: Add two numbers       # features/adding.feature:2
    Given the input "2+2"         # features/step_definitions/calculator_step.rb:1
    When the calculator is run    # features/step_definitions/calculator_step.rb:5
    Then the output should be "4" # features/step_definitions/calculator_step.rb:10
      TODO (Cucumber::Pending)
      ./features/step_definitions/calculator_step.rb:11:in `/^the output should be "(.*?)"$/'
      features/adding.feature:6:in `Then the output should be "4"'

1 scenario (1 pending)
3 steps (1 pending, 2 passed)
0m0.102s

现在我们给我们的最后一步添加断言:

Then(/^the output should be "(.*?)"$/) do |arg1|
    expect(@output).to eq 4
end

我们使用 RSpec 断言来检查 feature 中指定的期待值是否与程序的实际输出一致:

λ MacBook-Pro calculator → cucumber
Feature: Adding

  Scenario: Add two numbers       # features/adding.feature:2
    Given the input "2+2"         # features/step_definitions/calculator_step.rb:2
    When the calculator is run    # features/step_definitions/calculator_step.rb:6
    Then the output should be "4" # features/step_definitions/calculator_step.rb:11

      expected: 4
           got: ""

      (compared using ==)
       (RSpec::Expectations::ExpectationNotMetError)
      ./features/step_definitions/calculator_step.rb:12:in `/^the output should be "(.*?)"$/'
      features/adding.feature:6:in `Then the output should be "4"'

Failing Scenarios:
cucumber features/adding.feature:2 # Scenario: Add two numbers

1 scenario (1 failed)
3 steps (1 failed, 2 passed)
0m0.080s

好极了。现在我们的测试终于能够由于合理的理由失败了:它运行我们的程序,检查输出,告诉我们应该如何正确输出。我们已经为这个版本做了很多工走:当我们回到这个代码的时候,cucumber 将告诉我们该做做什么来让我们的程序生效。如果我们所有的需求都可以利用失败的测试来验证,创建软件将会容易很多。

通过测试 #

现在我们有了一个失败的 Cucumber 场景,是时候让我们的场景驱动我们解决问题。

试试下面的代码:

# cacl.rb
print "4"

终于成功了

λ MacBook-Pro calculator → cucumber
Feature: Adding

  Scenario: Add two numbers       # features/adding.feature:2
    Given the input "2+2"         # features/step_definitions/calculator_step.rb:2
    When the calculator is run    # features/step_definitions/calculator_step.rb:6
    Then the output should be "4" # features/step_definitions/calculator_step.rb:11

1 scenario (1 passed)
3 steps (3 passed)
0m0.095s

http://baya.github.io/2014/04/21/cucumber%E5%AE%9E%E6%88%98/