--------------------------------------------------------------------------------------------------------------------------------------
[翻译][155] Beginning With Cucumber
翻译:蜗牛 http://ywencn.iteye.com RailsQQ裙:42598811
Cucumber 是一个用来对Ruby On Rails应用进行行为驱动开发(BDD)的高级测试框架。Cucumber的独有特点是他使用英语(或者其他支持的语言,包括汉语)来定义程序的行为,在这一集我们将从头开始创建Rails 应用程序,并且使用Cucumber来定义其行为。
我们将首先创建一个新的Rails应用程序,并为这个例子使用最经典的例子来创建一个博客程序。首先我们使用最通常的方式来创建一个Rails项目。
rails blog
接下来,我们需要使用我们将要用到的这些gems来创建测试环境。
在 /config/environments/test.rb 的底部加入这些代码:
config.gem "rspec", :lib => false, :version => ">=1.2.2" config.gem "rspec-rails", :lib => false, :version => ">=1.2.2" config.gem "webrat", :lib => false, :version => ">=0.4.3" config.gem "cucumber", :lib => false, :version => ">=0.2.2"
除了Cucumber,我们还需要 Rspec 和 Webrat.他们都不是运行Cucumber必须的,但是他们可以很好的协同工作。当你熟悉使用Cucumber后你可以使用单元测试来代替Rspec或用一个不同的测试框架来代替Webrat.
现在我们在程序中已经指定了需要的gems ,我们通过运行rake命令来安装他们:
sudo rake gems:install RAILS_ENV=test
如果你还没安装过这些gems,他们将会被自动安装。安装完毕后我们就可以为应用程序建立Cucumber了。
script/generate cucumber
这个命令会在应用程序根目录下创建一个features文件夹。这就是我们定义应用程序行为的地方。
创建我们的第一个故事片 (感觉自己像导演吧!):
译注:关于feature究竟应该翻译成"功能"还是"故事片"或者别的什么,我觉得翻译成故事片更合适。因为接下来就是场景什么的,我们把自己当成是一个导演,演员就是我们应用程序的用户,告诉演员要干什么干什么。从这点上来讲,cucumber确实够酷。
在我们创建的这个博客程序中,我们想要管理文章,所以我们在features文件夹下新建一个叫 manage_atricles.feature的文件。(如果你使用TextMate作为开发工具,那么你可以去下载cucumber的tm bundle,地址http://github.com/bmabey/cucumber-tmbundle )
Cucumber的定义由两个部份组成。第一项是可选的,用来定义故事片情节本身,他们由3个部份组成:“In order”, “As a” 和 “I Want”. 我们像这样来定义我们的博客程序:
Feature: Manage Articles In order to make a blog As an author I want to create and manage articles
"In order"行定义了我们的总体目标,就是创建一个博客。"As a"行定义了一个条件,可以理解为故事片中的演员。最后一行是我们想要实现的具体功能,也就是这个故事片的主要情节。
Scenario: Articles List Given I have articles titled Pizza, Breadsticks When I go to the list of articles Then I should see "Pizza" And I should see "Breadsticks"
$ cucumber features -n Feature: Manage Articles In order to make a blog As an author I want to create and manage articles Scenario: Articles List Given I have articles titled Pizza, Breadsticks When I go to the list of articles Then I should see "Pizza" And I should see "Breadsticks" 1 scenario 3 skipped steps 1 undefined step You can implement step definitions for missing steps with these snippets: Given /^I have articles titled Pizza, Breadsticks$/ do pending end
场景步骤被定义在 features 文件中的step_definitions文件夹中。我们在这个文件中新建一个叫atricle_steps.rb的文件来定义我们的场景。
Given /^I have articles titled Pizza, Breadsticks$/ do pending end
我们的步骤被定义成一个正则表达式。它会定义这个场景种的文本语言并且返回成我们可以用到Ruby代码里去的一些东西。让这个步骤具有可以处理任何 传入的数据的能力是个不错的主意,所以我们要把正则表达式中的"Pizza,Breadsticks"替换成一个子表达式,然后匹配子表达式返回的每个标 题。
Given /^I have articles titled (.+)$/ do |titles| titles.split(', ').each do |title| Article.create!(:title => title) end end
我们将传入一些由逗号和空格分隔的可以被(.+)部份匹配的标题,并且传递给titles这个变量。然后,我们通过","把字符串分隔成单独的title。每个能匹配上的标题我们都会用来创建一篇新的文章。这就可以满足Given的条件了。
当我们运行Cucumber的时候,它告诉我们有一个步骤没有被定义,其他三个步骤被跳过。这意味着虽然他们没有被执行,但是这些步骤的定义是存在 的。如果是这样的话,那么他们该在哪里定义呢?答案是Webrat在和article步骤相同目录下的一个叫webrat_steps.rb的文件里定义 了一系列的步骤。这些步骤定义了一些常用的任务,比如进入到一个页面。
When /^I go to (.+)$/ do |page_name| visit path_to(page_name) end
这个步骤定义符合我们第二步(当我查看文章列表)并且还有符合其他被跳过的步骤的定义。
现在我们所有的步骤都定义好了,我们可以再次运行Cucumber,并且查看Cucumber的输出来决定我们下一步该做什么。
$ cucumber features -n Feature: Manage Articles In order to make a blog As an author I want to create and manage articles Scenario: Articles List Given I have articles titled Pizza, Breadsticks uninitialized constant Article (NameError) ./features/step_definitions/article_steps.rb:3:in `__instance_exec0' ./features/step_definitions/article_steps.rb:2:in `each' ./features/step_definitions/article_steps.rb:2:in `/^I have articles titled (.+)$/' features/manage_articles.feature:7:in `Given I have articles titled Pizza, Breadsticks' When I go to the list of articles Then I should see "Pizza" And I should see "Breadsticks" 1 scenario 1 failed step 3 skipped steps
这次我们看不那个未定义步骤了,取而代之的是一个失败的步骤。这个步骤之所以失败是因为我们还没有创建Article模型。我们将通过RSpec生成器来创建它。
script/generate rspec_model Article title:string content:text
译者注:windows用户请用 ruby script/generate xxxxxx
然后我们运行数据迁移,并且把开发数据库结构复制到测试数据库。
rake db:migrate rake db:test:clone
现在我们再次运行Cucumber。
$ cucumber features -n Feature: Manage Articles In order to make a blog As an author I want to create and manage articles Scenario: Articles List Given I have articles titled Pizza, Breadsticks When I go to the list of articles Can't find mapping from "the list of articles" to a path. Now, go and add a mapping in features/support/paths.rb (RuntimeError) /Users/eifion/rails/apps_for_asciicasts/blog/features/support/paths.rb:12:in `path_to' ./features/step_definitions/webrat_steps.rb:11:in `/^I go to (.+)$/' features/manage_articles.feature:8:in `When I go to the list of articles' Then I should see "Pizza" And I should see "Breadsticks" 1 scenario 1 failed step 2 skipped steps 1 passed step
我们的第一步通过了测试!但是第二步现在失败了。这是因为Cucumber不知道怎么把 "the list of articles" 对应到我们应用程序的路径。
Cucumber安装的时候在/features/support文件夹里会生成一个叫paths.rb的文件。在这个文件里我们可以自定义 Cucumber文件里的英语定义和我们的Rails应用程序路径之间的映射关系。要增加一条映射,我们只需要在path_to 方法里添加一个新的when条件到case语句。(文件里有一个注释来告诉你添加到哪里)
# Add more page name => path mappings here when /the list of articles/ articles_path
再次运行Cucumber的时候,会显示下一个需要修复的错误。这次他找不到符合articles_path的路由。我们需要把这个映射加入到routes.rb
map.resources :articles
完成以后,Cucumber告诉我们找不到ArticlesController。和模型一样,我们使用RSpec生成器来创建这个控制器。
script/generate rspec_controller articles index
Cucumber仍然不满意。他想在我们的视图里看到"Pizza",但是我们还没在index视图写任何东西所以Cucumber找不到他。我们要修改Articles控制器的index action 和 视图 代码来显示文章列表。
#The index action in the ArticlesController def index @articles = Article.all end
#The view code for the Articles index page. <h1>Articles</h1> <% @articles.each do |article| %> <h2><%= h(article.title) %></h2> <p><%= h(article.content) %></p> <% end %>
这回Cucumbe高兴啦!当我们再次运行他的时候他显示4个步骤全部通过。
看起来我们不得不为了一个Cucumber场景的通过付出大量努力,事实上我们采用了故意很小的步骤来展示Cucumber通常都告诉你下一步该干什么。当你熟练使用cucumber后,你可以在更大些的步骤完成后运行cucumber。
现在我们已经展示了cucumber大概是怎样工作的了。我们将继续另外一个更复杂的例子,并且使用更大的步骤。
Scenario: Create Valid Article Given I have no articles And I am on the list of articles When I follow "New Article" And I fill in "Title" with "Spuds" And I fill in "Content" with "Delicious potato wedges!" And I press "Create" Then I should see "New article created." And I should see "Spuds" And I should see "Delicious potato wedges!" And I should have 1 article
Given /^I have no articles$/ do Article.delete_all end Then /^I should have ([0-9]+) article$/ do |count| Article.count.should == count.to_i end
第一步直截了当写出来。为了满足那个条件,我们只需要删除所有的文章。第二步中,我们把数字1改成了一个可以匹配正数的正则表达式,并且把那个数字 当成一个变量传给代码块。然后代码块检查文章的数量是否等于那个传进来的变量。注意Cucumber会把接收到的任何正则表达式当成字符串处理,所以我们 需要显式的把count这个变量转换成integer类型。
Given I have no articles And I am on the list of articles When I follow "New Article" Could not find link with text or title or id "New Article" (Webrat::NotFoundError) (eval):2:in `click_link' ./features/step_definitions/webrat_steps.rb:19:in `/^I follow "([^\"]*)"$/' features/manage_articles.feature:15:in `When I follow "New Article"'
<p><%= link_to "New Article", new_article_path %></p>
<% form_for @article do |f| %> <ol class="formList"> <li> <%= f.label :title %> <%= f.text_field :title %> </li> <li> <%= f.label :content %> <%= f.text_field :content %> </li> <li> <%= f.submit "Create" %> </li> </ol> <% end %>
def new @article = Article.new end def create @article = Article.create!(params[:article]) flash[:notice] = "New article created." redirect_to articles_path end
Then I should see "New article created." expected the following element's content to include "New article created.": ArticlesSpudsDelicious potato wedges!New Article (Spec::Expectations::ExpectationNotMetError) ./features/step_definitions/webrat_steps.rb:94:in `/^I should see "([^\"]*)"$/' features/manage_articles.feature:19:in `Then I should see "New article created."'
<%= flash[:notice] %>
我们加上flash message后,再次运行cucumber,所有的步骤都绿了。
$ cucumber features -n Feature: Manage Articles In order to make a blog As an author I want to create and manage articles Scenario: Articles List Given I have articles titled Pizza, Breadsticks When I go to the list of articles Then I should see "Pizza" And I should see "Breadsticks" Scenario: Create Valid Article Given I have no articles And I am on the list of articles When I follow "New Article" And I fill in "Title" with "Spuds" And I fill in "Content" with "Delicious potato wedges!" And I press "Create" Then I should see "New article created." And I should see "Spuds" And I should see "Delicious potato wedges!" And I should have 1 article 2 scenarios 14 passed steps
现在我们的应用程序的行为和我们设计的一样了。随着这个场景接下来的步骤的通过,我们开始一个新的场景,觉一篇没有通过的文章。
在这里我们已经介绍了cucumber的基础部份。它看起来需要很多的工作来用英语定义测试,并且一次一次的完成每个步骤。但是cucumber测 试有很多有点。你用它测试你的真个堆栈,所以你写的测试包括了从用户界面到数据库的一切。他不是单元测试的替代物,他提供了一个写测试你的应用程序的整体 行为好方法。