Wednesday, November 12, 2008

Playing with Merb+Cucumber+Webrat (1)

In my previous post, we have setup the environment for playing with merb+cucumber+webrat. Now lets start playing !! Under ur merb project directory, place the following code in features/users.feature:
之前的文章中,我们设好了我们的 merb+cucumber+webrat 的环境。现在,让我们开始玩玩吧!! 在你的 merb 项目中,把以下的代码复制进features/users.feature:
Feature: Manage Users
To have the system support multiple users
I should be able to manage users in the system

Scenario: View Existing Users
Given the following 5 users exist in the system:
| name | email |
| Peter | peter@abc.com |
| John | john@xyz.org |
| Jane | jane@efg.net |
| Mary | mary@hij.info |
| Cathy | cathy@stu.us |
When I go to /users
Then I should see all 5 users
And all users should be ordered by name
And I should see Peter having "Email" as "peter@abc.com"
Yup, pretty basic for any system with basic user support. To run the above feature, you can use one of the followings:
的确是非常简单的管理用户的 feature,接下来,让我们在终端中执行以下的指令:

#$ rake feature[/users]
#$ rake features

The difference is that the 1st command runs only the specified feature, while the later runs all. Usually when u are focusing on a feature, you will run the 1st command. In ur console, you will get the following output (i'm leaving out the colors to keep my life simple):
这两个指令的分别在于,前者只运行一个 feature 文件,而后者将运行所有的 features。当你正开发某个 feature 时,运行前者将帮你省下很多时间。让我们看看以下的终端的输出:

Feature: Manage Users  # features/users.feature
To have the system support multiple users
I should be able to manage users in the system
Scenario: View Existing Users # features/users.feature:5
Given the following 5 users exist in the system: # features/users.feature:6
When I go to /users # features/steps/common_webrat.rb:4
Then I should see all 5 users # features/users.feature:14
And all users should be ordered by name # features/users.feature:15
And I should see Peter having email "peter@abc.com" # features/users.feature:16

1 steps skipped
4 steps pending

You can use these snippets to implement pending steps:

Given /^the following 5 users exist in the system:$/ do
end

Then /^I should see all 5 users$/ do
end

Then /^all users should be ordered by name$/ do
end

Then /^I should see Peter having email "peter@abc.com"$/ do
end

Finished in 0.006268 seconds

0 examples, 0 failures

From your console, you will probably notice some very nice and intuitive colorings, and since they are very intuitive, i shall skip over them. Notice that for pending (yellow) steps, cucumber tries to be helpful by suggesting how you are going to write them. Now, let's take cucumber's hint, copy-and-paste those suggested lines, and open features/steps/users_steps.rb:
发现了没,输出的字句都用了非常直观的的颜色来帮你识别其中的意义,而我也不再这>里多加说明。看见了针对黄色的 pending 输出,cucumber 试着给了你一些提示,让你知道相关的 step 该怎么编写。那我们就接受它的提示,将这些提示代码复制到 features/steps/users_steps.rb:
Given /^the following 5 users exist in the system:$/ do
end
Then /^I should see all 5 users$/ do
end
Then /^all users should be ordered by name$/ do
end
And do:
接着运行:

#$ rake feature[/users]

Feature: Manage Users  # features/users.feature
To have the system support multiple users
I should be able to manage users in the system
Scenario: View Existing Users # features/users.feature:5
Given the following 5 users exist in the system: # features/steps/users_steps.rb:1
expected 0 block argument(s), got 1 (Cucumber::ArityMismatchError)
features/steps/users_steps.rb:1:in `/^the following 5 users exist in the system:$/'
features/users.feature:6:in `Given the following 5 users exist in the system:'
When I go to /users # features/steps/common_webrat.rb:4
Then I should see all 5 users # features/steps/users_steps.rb:4
And all users should be ordered by name # features/steps/users_steps.rb:7

1 steps failed
4 steps skipped

OH NO !! What have we done wrong ?? No worries, we are on the right track :]. The red failure lines are intended. Let's fix the problem by editing features/steps/users_steps.rb:
真糟糕,我们做错了什么吗??别担心,一切都在掌握中。红色失败是意料中的,让我们改改 features/steps/users_steps.rb 来搞定它吧:
Given /^the following 5 users exist in the system:$/ do |attrs_table|
end
Then /^I should see all 5 users$/ do
end
Then /^all users should be ordered by name$/ do
end
Then /^I should see Peter having "Email" as "peter@abc.com"$/ do
end
And do:
再来运行:

#$ rake feature[/users]

Feature: Manage Users  # features/users.feature
To have the system support multiple users
I should be able to manage users in the system
Scenario: View Existing Users # features/users.feature:5
Given the following 5 users exist in the system: # features/steps/users_steps.rb:1
When I go to /users # features/steps/common_webrat.rb:4
Then I should see all 5 users # features/steps/users_steps.rb:4
And all users should be ordered by name # features/steps/users_steps.rb:7
And I should see Peter having "Email" as "peter@abc.com" # features/steps/users_steps.rb:10

5 steps passed

OH NO !! What have we done wrong ?? Just adding attrs_table and we get a 'passed' for all steps ?? We've not written any model/view/controller code yet, so how can ... One key point to note here is that as long as we have a matching step, and the step doesn't trigger a failure, it is considered a 'passed'. Anyway, there is a workaround for this. Since cucumber does not yet support any pending/todo in a step, we can rewrite features/steps/users_steps.rb:
搞什么东东??怎么这下子我们都拿到了绿色的成功?我们好像连 model/view/controller 的代码都没写!!在这里,我们知道了,只要我们有个匹配的 step,不管>里头的内容是什么,只要没产生错误,cucumber 都会把它视为一个绿色成功的 step。就现在的 cucumber 版本,我们可以双点小聪明,修改一下 features/steps/users_steps.rb:
Given /^the following 5 users exist in the system:$/ do |attrs_table|
pending
end
Then /^I should see all 5 users$/ do
pending
end
Then /^all users should be ordered by name$/ do
pending
end
Then /^I should see Peter having "Email" as "peter@abc.com"$/ do
pending
end
And do:
再来运行:

#$ rake feature[/users]

Feature: Manage Users  # features/users.feature
To have the system support multiple users
I should be able to manage users in the system
Scenario: View Existing Users # features/users.feature:5
Given the following 5 users exist in the system: # features/steps/users_steps.rb:1
undefined local variable or method `pending' for # (NameError)
./features/steps/users_steps.rb:2:in `Given /^the following 5 users exist in the system:$/'
features/users.feature:6:in `Given the following 5 users exist in the system:'
When I go to /users # features/steps/common_webrat.rb:4
Then I should see all 5 users # features/steps/users_steps.rb:4
And all users should be ordered by name # features/steps/users_steps.rb:7
And I should see Peter having "Email" as "peter@abc.com" # features/steps/users_steps.rb:10

1 steps failed
4 steps skipped

Pretty ugly, are we back to square one? Not really, as long as we are well aware of the actual reason. Let's take a break take a look at how Aslak (cucumber's author) sums up the style of development with BDD and cucumber:
  1. when u have a new feature to implement, start by writing a new feature (a Feature in features/*.feature file) or scenario (a Scenario under an existing features/*.feature)
  2. describe the feature/scenario by filling in Then, followed by When, and finally Given
  3. run the features (make sure the new steps are yellow or red)
  4. write code to address the yellow/red steps
  5. repeat 3-4 to get all green steps
  6. when it comes to the nitty gritty details of your classes, use rspec to write specs before writing code
有点难看,我们又回到原点了吗?不一定,只要我们知道其背后的意义就暂时可以将就一下吧。让我们抛开代码,来看看 Aslak(cucumber 的作者)如何阐述使用 BDD 的开发模式和 cucumber 来开发:
  1. 当你有一个新的功能需要实现时,先把它写在一个新的 feature (即一个 features/*.feature 文件里的 Feature)或 scenario (在现有的 features/*.feature 添加一个 Scenario
  2. 编写/形容一下这个新的 feature/scenario,先从 Then 着手,让后使用 When,最后才用 Given
  3. 运行 features (确保它们是红色或黄色的)
  4. 针对这些红色或黄色的 steps,编写相关的代码
  5. 重负 3-4,知道你看见所有的 steps 都成绿色
  6. 针对一些非常小的细节,建议使用 rspec,不过,记得要先写 specs,让后才些代码
I shall not go into the iterating of writing codes and running the features since this will well go into many many pages, but before i end this post, let's take a look at how we can rewrite features/steps/users_steps.rb to make it more generic, less coupled to users:
好啦,接下来就是完成这些 steps 的内容,让后再来写代码了。太烦琐了,我也不想花再多的篇幅来编写。不过,在我结束前,我们可以再看看 features/steps/users_steps.rb 里的 steps,或许可以改一改来让它们更不被 users 绑死:
Given /^the following (\d+) (\w+) exist in the system:$/ do | count, resource, attrs_table|
pending
end
Then /^I should see all (\d+) (\w+)$/ do | count, resources |
pending
end
Then /^all (\w+) should be ordered by (\w+)$/ do | resources, attribute |
pending
end
Then /^all (\w+) should be ordered by (\w+)$/ do | resources, attribute |
pending
end
Then /^I should see (\w+) having "(.*)" as "(.*)"$/ do | resource_nick, attribute, value |
pending
end
With the above, the steps are more generic and we can probably placed them into features/steps/common_result_steps.rb, and remove features/steps/users_steps.rb. Should we call it refactoring of our steps ?
不错吧,程序员就是应该这样,在合理的范围里找捷径!!而既然这些 steps 已不再和 users 那么的紧紧相连,我们可以把它们放在 features/steps/common_result_steps.rb 里。 这算不算 steps 的重构?

The sample codes for this exercise (tag 0.1.x) can be downloaded from:
如果你想看看我是如何完成这个练习的,你可以下载相关代码(tag 为 0.1.x):
http://github.com/ngty/ty_cucumber_salad

No comments: