Now that we have learned some basic Ruby syntax and gained some understanding about what the Rails generate scaffold script does, it is high time we started using a more modern approach to coding. In fact, if you recall at the end of day 2, I realized with horror that we had actually modified code and added features without developing the tests first. This defied everything I had ever heard about good coding practices from the Ruby crowd and I set off to mend my ways.
Rick Denatale describes the process of test-driven/behavior-driven development as:
- Write the test/spec
- Ensure that it FAILS
- Write the code to make it pass
- Goto step 1
After reading a bit about test- and behavior-driven development, I decided to use a relatively new framework called cucumber which uses natural language to describe features.
Today we will:
- Install cucumber
- Set up the application
- Describe a feature
- Execute the feature and Watch it fail
- Write the code to make it pass
- Review what we learned
h1 {font-size: 150%}
h1,h2 {font-style: bold}
img
{
border:2px solid silver;
margin:0px 0px 15px 20px;
}
blockquote, pre.code {
border: solid 1px #aaa;
padding: 6px;
background-color: #eee;
color: inherit;
overflow:auto;
margin: 10px 0px;
}
Install Cucumber
Based on these install instructions
sudo gem install rspec rspec-rails cucumber webrat
important: Cucumber 0.1.12 and up depends on Webrat 0.3.2.1 or higher, which as of this writing is not yet officially released to Rubyforge’s gem repository. In the meanwhile, install Bryan Helkamp’s snapshot gem:
gem sources -a http://gems.github.com sudo gem install brynary-webrat
The plugins’ dependencies must be installed separately:
gem install term-ansicolor treetop diff-lcs nokogiri
Setup the Application
First we’ll create the Rails “to do list” application:
cd $webroot rails -d mysql todolist cd todolist rake db:create:all rake db:migrate
Now we’ll set up cucumber for the project
ruby script/generate cucumber create features/step_definitions create features/step_definitions/webrat_steps.rb create features/support create features/support/env.rb exists lib/tasks create lib/tasks/cucumber.rake create script/cucumber
Just to make sure that everything is installed correctly:
rake features
If that runs without errors you are ready to rock.
Describe a Feature
In the features directory that was auto-created for us with the cucumber script, we create a .feature file which starts with a description of the feature. The first section that describes the feature appears to be purely documentation; however the “scenario” sections will each become part of the executable feature definition. For starters we’ll do something simple.
features/tasklist.feature
Feature: Tasks In order to keep track of tasks People should be able to Create a list of tasks Scenario: List Tasks Given that I have created a task "task 1" When I go to the tasks page Then I should see "task 1"
We know we haven’t written any executable steps, but we’ll execute it anyhow:
Note that one of the steps is already defined in webrat. Isn’t that cool? When we set up cucumber for the project, it automatically includes step_definitions/webrat_steps.rb which defines some common steps. As you get the hang of this, you reuse certain word patterns which map to specific tests. But we’re getting ahead of ourselves. We need to dive into the creation of “steps” which make up our executable spec. Cucumber gives a some handy snippets to get us started (in the output of “rake features” above). We’ll paste these into a new file that we’ll create in the “features/step_definitions” directory:
features/step_definitions/tasklist_steps.rb
Given /^that I have created a task "(.*)"$/ do |desc| Task.create!(:description => desc) end When /^I go to the tasks page$/ do visit "/tasks" end
Note that I touched up the first step to include a regular expression. This means I could add Given that I have created a task "foo"
to another scenario and it would match this step.
Short aside on task creation syntax
To create the task, I’m calling my Task model directly (since I’m new to Rails, I looked up the ActiveRecord::Base syntax in the Rails Framework API docs). In my first pass I wrote:
task = Task.new(:description => desc); task.saveHowever, Aslak Hellesøy kindly pointed out that it would fail silently with that syntax, and instead I should call
task.save!
or the even simplerTask.create!(:description => desc)
. I had missed create! in the documentation, since it is part of ActiveRecord::Validations. The API doc is a little confusing on this point, but looking at the source shows that ActiveRecord::Validations is included as a module. Pat Maddox notes that he uses the bang version (.save!) in tests, and the non-bang version (.save) in production code since validation errors aren’t exceptional.
Back to Step 3
Looking in features/step_definitions/webrat_steps.rb
, you can see the definition of our third step:
Then /^I should see "(.*)"$/ do |text| response.body.should =~ /#{text}/m end
Ok, now we have a simple spec. Is it time to write the code? No!
Execute the Feature and Watch it Fail
As expected, we see errors on our first step, since we have not yet written any code for the application.
Write the code to make it pass
Now, at last it is time to write code
$ ./script/generate scaffold Task description:string $ rake db:migrate
Run the spec again..
It passes, yay!
What did we learn?
When we first set up our app, to setup cucumber:
ruby script/generate cucumber
To describe our feature, we create two files:
- features/xxx.feature
- features/step_definitions/xxx_steps.rb
To run the feature description:
rake features
situated learning through open source
I just read Situated Learning: Legitimate Peripheral Participation by Jean Lave and Etienne Wenger, which I put on my wish list since I am a total geek about theories of how people learn, particularly with regard to social learning. It’s a challenging …
Great tutorial. I have been really struggling to find this type of step by step guide that is both up to date and covers BDD. Thanks very much.
Two minor things: When I did the rake db:migrate only the development database was migrated and the tests are run on production by default. Once I changed RAILS_ENV and ran the migration again all worked.
Second: I think the webrat dependency problem has been sorted out. I just used the gem install version and the example worked OK.
Great tutorial. However, webrat has gotten ahead of you, I think. With webrat 1.12, it generated a
#When /^I go to (.+)$/ do |page_name|
# visit path_to(page_name)
#end
which I just commented out to finish the tutorial. Properly, I suspect I should have worked out path_to, but I was focused on making the tutorial work…
Hello! I have another problem on winXP. After running rake featurer in your “Describe a Feature” chapter, this is what I got. It’s OK, but there are no letters ‘a’ !!!!!
Any ideas?
Code begin:
E:SOURCERAILSrails-2-day-3-behavior-driven-developmenttodolist>rake features
(in E:/SOURCE/RAILS/rails-2-day-3-behavior-driven-development/todolist)
´?? # fetures/tsklist.feture
Feture: Tsks
In order to keep trck of tsks
People should be ble to
Crete list of tsks
Scenrio: List Tsks # fetures/tsklist.feture:7
Given tht I hve creted tsk “tsk 1” # fetures/tsklist.feture:8
When I go to the tsks pge # fetures/step_definitions/webrt_st
eps.rb:6
Then I should see “tsk 1” # fetures/step_definitions/webrt_s
teps.rb:89
1 scenrio
2 steps skipped
1 step pending (1 with no step definition)
You cn use these snippets to implement pending steps which hve no step definitio
n:
Given /^tht I hve creted tsk “tsk 1″$/ do
end
E:SOURCERAILSrails-2-day-3-behavior-driven-developmenttodolist>
Thank you for this tutorial.
Webrat now has a “When I go to(.+)$/” step.
If you put the path webrat should visit in features/support/paths.rb, then webrat should be able to folow the “When I go to the tasks page” directive.
The missing “a” on Windows is a known bug (I’m too lazy to provide a url).
To resolve the missing ‘a’ issue, see: http://wiki.github.com/aslakhellesoy/cucumber/troubleshooting
Pingback: Cucumber quicksetup at Pablo Cantero
Sarah, thanks so much for sharing your efforts. One question I have is this:
Do you have to use the ruby-rails framework to get webrat to work? Can you just use garden-variety ruby? It seems from all the sites I have visited, and the Rspec/Cucumber book I’m using that webrat is tightly coupled to Ruby-rails.
Any insights you could share would be most appreciated.
I believe webrat is designed to work with rack apps, so it would work with Sinatra, but not PHP. If you want to work with a non-Ruby webapp, look at Capybara using the Mechanize driver. I wrote a short blog post about that here: /sarahblog/2011/02/legacy-testing-with-capybaramechanize/