Automating Features with cucumber
cucumber
- Given /^I am not yet playing$/ do
- pending # express the regexp above with the code you wish you had
- end
这就是一个cucumber的步骤定义。你可以把它想成是一个方法,然后在definition中定义一个这样的方法。
- mkdir features/step_definitions
- vi features/step_definitions/codebreaker_steps.rb
把上面的内容拷贝进去,并且删除pending一行。
- Given /^I am not yet playing$/ do
- end
执行下面的命令
- cucumber features/codebreaker_start_game.feature
- Feature: code-breaker starts game
- As a code-breaker
- I want to start a game
- So that I can break the code
- Scenario: start game # features/codebreaker_start_game.feature:7
- Given I am not yet playing # features/step_definitions/codebreaker_steps.rb:1
- When I start a new game # features/codebreaker_start_game.feature:9
- Then I should see "Welcome to Codebreaker!" # features/codebreaker_start_game.feature:10
- And I should see "Enter guess:" # features/codebreaker_start_game.feature:11
- But I should not see "What is your question?" # features/codebreaker_start_game.feature:12
- 1 scenario (1 undefined)
- 5 steps (4 undefined, 1 passed)
- 0m0.005s
- You can implement step definitions for undefined steps with these snippets:
- When /^I start a new game$/ do
- pending # express the regexp above with the code you wish you had
- end
- Then /^I should see "(.*?)"$/ do |arg1|
- pending # express the regexp above with the code you wish you had
- end
- Then /^I should not see "(.*?)"$/ do |arg1|
- pending # express the regexp above with the code you wish you had
- 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方法中需要我们创建一个新游戏,然后开始这个游戏。
- When /^I start a new game$/ do
- Codebreaker::Game.new.start
- end
这时候,我们的应用还没有任何代码,我们只是按照我们想要的方式来写。我们希望简单,尽可能的简单。
The code you wish you had.
再次运行
- cucumber features/codebreaker_start_game.feature
你会发现一些红色的信息
uninitialized constant Codebreaker (NameError)
因为你还没有定义Codebreaker以及Game以及start方法。好吧,我们来定义一下。
- mkdir -p lib/codebreaker/
- vi lib/codebreaker/game.rb
输入下面的内容
- module Codebreaker
- class Game
- def start
- end
- end
- end
再次执行
- cucumber features/codebreaker_start_game.feature
还是出现之前的红色信息。
这是因为按照约定,在lib目录需要有一个以顶级module名称命名的文件。
- vi lib/codebreaker.rb
写入下面的内容
- require 'codebreaker/game'
- vi features/support/env.rb
写入下面的内容
- $LOAD_PATH << File.expand_path('../../../lib', __FILE__)
- require 'codebreaker'
再次执行
- cucumber features/codebreaker_start_game.feature
看到的内容又变绿色了。
在完成第二个步骤之后,我们来看看Then这个步骤。
在这个步骤中,我们希望在console中看到Welcome to Codebreaker!。这意味着我们需要一个工具,来捕获Game发送到STDOUT的信息。
- vi features/step_definitions/codebreaker_steps.rb
加入下面的内容
- Then /^I should see "(.*?)"$/ do |message|
- output.messages.should include(message)
- end
修改When的内容
- When /^I start a new game$/ do
- game=Codebreaker::Game.new(output)
- game.start
- end
增加下面的内容
- class Output
- def messages
- @messages ||= []
- end
- def puts(message)
- messages << message
- end
- end
- def output
- @output ||= Output.new
- end
再次执行
- cucumber features/codebreaker_start_game.feature
提示我们wrong number of arguments(1 for 0)(ArgumentError)
是因为我没有定义带有参数的initialize。
- vi lib/codebreaker/game.rb
用下面的内容替换
- module Codebreaker
- class Game
- def initialize(output)
- end
- def start
- end
- end
- end
再次执行
- cucumber features/codebreaker_start_game.feature
这时候提示我们有逻辑错误,因为我们的game还没有完成。我们会在后面完成。
总结
我们先在.feature文件中写一个feature的scenario,已经scenario的steps,然后在step_definitions中定义Given,When,Then,然后在通过测试cucumber .feature文件来驱动我们编写实现代码。
当目前为止,我们已经学会使用cucumber从外部描述一件事。在接下来的章节中,我们将会进行从外到里的工作方式,使用RSpec来驱动单个对象的外部行为。