[翻译] Railscasts[155] Beginning With Cucumber

--------------------------------------------------------------------------------------------------------------------------------------

 

[翻译][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文件夹。这就是我们定义应用程序行为的地方。

Creating Our First Feature

创建我们的第一个故事片 (感觉自己像导演吧!):

译注:关于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"行定义了一个条件,可以理解为故事片中的演员。最后一行是我们想要实现的具体功能,也就是这个故事片的主要情节。

 

现在我们来定义一些这个故事片种的一些行为(情节),这是通过一个或者多个场景来实现的。场景通常按照一种相似的方式来设计,现在我们用“Given”,"When" 和 "Then"三个部份来组成场景。我们要写一个列出博客文章的场景。

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,看它的返回的输出。我们用-n 选项来让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"

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
 
Cucumber显示了我们的剧本纲要和场景演出的结果。其中3个步骤被跳过,1个步骤未定义。Cucumber不知道怎么运行没定义的步骤,因为我们还没有写Ruby代码来执行。Cucumber提供了一段我们可以直接粘贴到我们的程序的代码模板。

Defining The “Given” Step

定义"Given"步骤

场景步骤被定义在 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的条件了。

 

 

What About The Other Steps?

其他的步骤呢?

 

 

当我们运行Cucumber的时候,它告诉我们有一个步骤没有被定义,其他三个步骤被跳过。这意味着虽然他们没有被执行,但是这些步骤的定义是存在 的。如果是这样的话,那么他们该在哪里定义呢?答案是Webrat在和article步骤相同目录下的一个叫webrat_steps.rb的文件里定义 了一系列的步骤。这些步骤定义了一些常用的任务,比如进入到一个页面。

 

 

When /^I go to (.+)$/ do |page_name|
  visit path_to(page_name)
end

 

这个步骤定义符合我们第二步(当我查看文章列表)并且还有符合其他被跳过的步骤的定义。

 

Making It Turn Green, Step By Step

一步一步的把他变绿

现在我们所有的步骤都定义好了,我们可以再次运行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


这个场景定义了新建一个文章的行为,但是和刚才那个不同,他包含了我们应用程序的很多个页面。这个场景假设我们刚开始没有文章,然后点击"New Article"这个文字链接,提填写表单然后提高,然后然后希望看到这篇新文章。并且在这个时候数据库中有一篇文章。

和平常一样,我们的第一部是运行Cucumber. 运行后他告诉我们有两个没有定义的步骤和6个跳过的步骤。两个未定义的步骤是第一个("Given I have no articles")和第二个("And I should have 1 article")。 其他6个被包含在Webrat步骤中。

首先我们要创建那两个没定义的步骤.

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类型。

 

再次运行Cucumber,显示前面两个步骤现在通过测试,但是第三步失败。

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"'
 

这步失败是因为cucumber找不到一个叫"New Artical"的文字链接。我们需要在index页和新文章页之间创建一个链接并且创建这个页面。


我们在index页上创建一个指向新文章页的链接。

<p><%= link_to "New Article", new_article_path %></p>  
 

然后在/views/articles/下面的new.html.erb中为新文章创建一个表单。

<% 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  
 

我们在为控制器编写new和create action的时候迈了一大步。我们来再次用cucumber运行这个场景,然后看还有啥要做的。这回前面6个步骤都通过了,然后就遇到这个失败的步骤。

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."'
 

虽然我们在create action种创建了flash message,但是我们并没有在index页面里显示出来。加上它应该就可以让这步测试通过。它应当放在模板文件里,但是由于我们的目标只是让这步测试通过,所以我们放在index页面里。

<%= 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测 试有很多有点。你用它测试你的真个堆栈,所以你写的测试包括了从用户界面到数据库的一切。他不是单元测试的替代物,他提供了一个写测试你的应用程序的整体 行为好方法。

 

 

你可能感兴趣的:(正则表达式,单元测试,Ruby,Rails,rspec)