Automating Features with cucumber

cucumber


  
  1. Given /^I am not yet playing$/ do
  2. pending # express the regexp above with the code you wish you had
  3. end


这就是一个cucumber的步骤定义。你可以把它想成是一个方法,然后在definition中定义一个这样的方法。


  
  1. mkdir features/step_definitions
  2. vi features/step_definitions/codebreaker_steps.rb



把上面的内容拷贝进去,并且删除pending一行。


  
  1. Given /^I am not yet playing$/ do
  2. end



执行下面的命令


  
  1. cucumber features/codebreaker_start_game.feature

  
  1. Feature: code-breaker starts game
  2. As a code-breaker
  3. I want to start a game
  4. So that I can break the code
  5. Scenario: start game # features/codebreaker_start_game.feature:7
  6. Given I am not yet playing # features/step_definitions/codebreaker_steps.rb:1
  7. When I start a new game # features/codebreaker_start_game.feature:9
  8. Then I should see "Welcome to Codebreaker!" # features/codebreaker_start_game.feature:10
  9. And I should see "Enter guess:" # features/codebreaker_start_game.feature:11
  10. But I should not see "What is your question?" # features/codebreaker_start_game.feature:12
  11. 1 scenario (1 undefined)
  12. 5 steps (4 undefined, 1 passed)
  13. 0m0.005s
  14. You can implement step definitions for undefined steps with these snippets:
  15. When /^I start a new game$/ do
  16. pending # express the regexp above with the code you wish you had
  17. end
  18. Then /^I should see "(.*?)"$/ do |arg1|
  19. pending # express the regexp above with the code you wish you had
  20. end
  21. Then /^I should not see "(.*?)"$/ do |arg1|
  22. pending # express the regexp above with the code you wish you had
  23. end


结果告诉我们有一个scenario,五个步骤,四个还没有定义,一个已经通过。

cucumber命令的参数是features/codebreaker_start_game.feature,在cucumber启动的时候会加载目录下所有的.rb文件,已经子目录下面的.rb文件,包括前面的features/step_definitions/codebreaker_steps.rb。

我们通过调用cucumber提供的五个方法来定义feature中的步骤:Given(), When(), Then(), And(), But()。And()和But()会呈现跟在前面的Given, When, Then的意思。在上面的例子中,scenario中最后两行的And和But被当做是Then来处理。

每个方法都通过正则表达来匹配。

Given方法对应的正则表达式是:/^I am not yet playing$/。就会匹配feature中的Given部分。

When方法中需要我们创建一个新游戏,然后开始这个游戏。


  
  1. When /^I start a new game$/ do
  2. Codebreaker::Game.new.start
  3. end



这时候,我们的应用还没有任何代码,我们只是按照我们想要的方式来写。我们希望简单,尽可能的简单。
The code you wish you had.

再次运行


  
  1. cucumber features/codebreaker_start_game.feature


你会发现一些红色的信息
uninitialized constant Codebreaker (NameError)

因为你还没有定义Codebreaker以及Game以及start方法。好吧,我们来定义一下。


  
  1. mkdir -p lib/codebreaker/
  2. vi lib/codebreaker/game.rb



输入下面的内容


  
  1. module Codebreaker
  2. class Game
  3. def start
  4. end
  5. end
  6. end



再次执行


  
  1. cucumber features/codebreaker_start_game.feature



还是出现之前的红色信息。

这是因为按照约定,在lib目录需要有一个以顶级module名称命名的文件。


  
  1. vi lib/codebreaker.rb



写入下面的内容


  
  1. require 'codebreaker/game'




  
  1. vi features/support/env.rb



写入下面的内容


  
  1. $LOAD_PATH << File.expand_path('../../../lib', __FILE__)
  2. require 'codebreaker'


再次执行


  
  1. cucumber features/codebreaker_start_game.feature



看到的内容又变绿色了。

在完成第二个步骤之后,我们来看看Then这个步骤。

在这个步骤中,我们希望在console中看到Welcome to Codebreaker!。这意味着我们需要一个工具,来捕获Game发送到STDOUT的信息。


  
  1. vi features/step_definitions/codebreaker_steps.rb



加入下面的内容


  
  1. Then /^I should see "(.*?)"$/ do |message|
  2. output.messages.should include(message)
  3. end



修改When的内容


  
  1. When /^I start a new game$/ do
  2. game=Codebreaker::Game.new(output)
  3. game.start
  4. end



增加下面的内容


  
  1. class Output
  2. def messages
  3. @messages ||= []
  4. end
  5. def puts(message)
  6. messages << message
  7. end
  8. end
  9. def output
  10. @output ||= Output.new
  11. end



再次执行


  
  1. cucumber features/codebreaker_start_game.feature


提示我们wrong number of arguments(1 for 0)(ArgumentError)

是因为我没有定义带有参数的initialize。


  
  1. vi lib/codebreaker/game.rb



用下面的内容替换


  
  1. module Codebreaker
  2. class Game
  3. def initialize(output)
  4. end
  5. def start
  6. end
  7. end
  8. end



再次执行


  
  1. cucumber features/codebreaker_start_game.feature



这时候提示我们有逻辑错误,因为我们的game还没有完成。我们会在后面完成。

总结

我们先在.feature文件中写一个feature的scenario,已经scenario的steps,然后在step_definitions中定义Given,When,Then,然后在通过测试cucumber .feature文件来驱动我们编写实现代码。

当目前为止,我们已经学会使用cucumber从外部描述一件事。在接下来的章节中,我们将会进行从外到里的工作方式,使用RSpec来驱动单个对象的外部行为。


教程参考:The RSpec Book


所有的代码放在
https://github.com/woaigithub/the-rspec-book-with-cucumber