Getting Started with Padrino and BDD

Padrino is a web framework, closely related to its cousin, the Sinatra web framework. It tries to offer the flexibility of development of Sinatra and a wide array of helpers, tools and add-ons of Rails.

Padrino, Italian for “godfather”, is built on top of Sinatra, so you can use all your knowledge gained from the previous Sinatra series published on this site. However, it is somewhat different, as it provides some conventions that look very similar to the ones found in the previously mentioned articles, as you will see. By the way, I will use Ruby 1.9.2 for this tutorial.

To make it easier, I’ll manage it via RVM. The steps to set up RVM, if you haven’t already, are outlined in this article by Glenn Goodrich of Rubysource fame. Once you have RVM and Ruby 1.9.2 installed, make a gemset for this application. I am calling mine “rubysource”, as you can see below:

$ rvm use 1.9.2@rubysource --create

Install Padrino

To install Padrino:

$ gem install padrino

Once done, create a new project by running:

$ padrino g project rubytoday -t cucumber -b

g is short form for generate

-t is a switch used to specify a testing framework, in this case
I’ve specified Cucumber, this also includes RSpec

-b tells Padrino to use Bundler for managing the gems

This creates a project named rubytoday.

Once you change to the rubytoday directory, if you’ve used Rails, you’ll see a somewhat similar directory structure to a Rails project:

,-- Gemfile
|-- Gemfile.lock
|-- app
|   |-- app.rb
|   |-- controllers
|   |-- helpers
|   `-- views
|       `-- layouts
|-- config
|   |-- apps.rb
|   `-- boot.rb
|-- config.ru
|-- cucumber.yml
|-- features
|   |-- add.feature
|   |-- step_definitions
|   |   `-- add_steps.rb
|   `-- support
|       |-- env.rb
|       `-- url.rb
|-- public
|   |-- favicon.ico
|   |-- images
|   |-- javascripts
|   `-- stylesheets
|-- spec
|   |-- spec.rake
|   `-- spec_helper.rb
`-- tmp

Some notes:

  • the main app located under app directory
  • JavaScript and stylesheets content under public directory
  • Cucumber features under features directory
  • RSpec folder under spec directory

Now that we have the initial bare app, it’s a good time to check it into Git. That way, you can revert to this state again should the need arise. Before we do that, open the .gitignore file and add a regular expression pattern of the backup files that your editor creates. In my case that’s Emacs and the backup files have an ending of: *~.

Then add it to Git, like so:

$ git init
$ git add .
$ git commit -m "initial commit"
$ git status</p>

<h1>On branch master</h1>

<p>nothing to commit (working directory clean)

The last command git status states that I’m currently on the main master branch. To do further work, I will create a new branch and do my work within it. Once I’m happy with it, I’ll merge it back into the master branch.

$ git checkout -b hello_world
Switched to a new branch 'hello_world'

With the command git checkout -b helloworld I created a new branch helloworld and switched to it.

The output from git branch confirms it:

$ git branch
* hello_world
  master

The initial ‘*’ in front of the branch name states the branch in which I’m currently working.

Hello world in BDD

If the branch name is anything to go by, you probably guessed what the next part of the tutorial will show. The creation of the “Hello world” page.

Since this tutorial tries to teach the usage of Cucumber and Behaviour Driven Development, I’ll start by writing a feature file explaining how I wish my application to behave.

Under the features directory I open a file hello.feature and add:

Feature: hello world
  I want the application to greet the world every time I use it

  Scenario: greet the world
    Given I visit the app
    Then it should greet the world

Feature and Scenario are keywords. Feature is the main title, as it were, that briefly outlines what is being described, followed by a description. Scenario is an example of how a particular example should manifest.
There is also a file called add.feature, which isn’t needed, so I’ll remove it:

$ git rm add.feature

Then I run Cucumber, to see the output:

$ bundle exec cucumber
Using the default profile...
Feature: hello world
  I want the application to greet the world every time I use it

Scenario: greet the world        # features/hello.feature:4
    Given I visit the app          # features/hello.feature:5
      Undefined step: "I visit the app" (Cucumber::Undefined)
      features/hello.feature:5:in 'Given I visit the app'
    Then it should greet the world # features/hello.feature:6
      Undefined step: "it should greet the world" (Cucumber::Undefined)
      features/hello.feature:6:in 'Then it should greet the world'

1 scenario (1 undefined)
2 steps (2 undefined)
0m0.002s

You can implement step definitions for undefined steps with these snippets:

Given /^I visit the app$/ do
  pending # express the regexp above with the code you wish you had
end

Then /^it should greet the world$/ do
  pending # express the regexp above with the code you wish you had
end

The Cucumber complains that it does not recognise the undefined steps. So I copy them and add them to a new file:
features/stepdefinitions/hellosteps.rb:

Given /^I visit the app$/ do
  pending # express the regexp above with the code you wish you had
end

Then /^it should greet the world$/ do
  pending # express the regexp above with the code you wish you had
end

When I run the Cucumber again:

$ bundle exec cucumber
Using the default profile...
Feature: hello world
  I want the application to greet the world every time I use it

Scenario: greet the world        # features/hello.feature:4
    Given I visit the app          # features/step_definitions/hello_steps.rb:1
      TODO (Cucumber::Pending)
      ./features/step_definitions/hello_steps.rb:2:in '/^I visit the app$/'
      features/hello.feature:5:in 'Given I visit the app'
    Then it should greet the world # features/step_definitions/hello_steps.rb:5

1 scenario (1 pending)
2 steps (1 skipped, 1 pending)
0m0.002s

It fails on the first step and skips the second one. Since the first step doesn’t really have anything defined, I add the following:

Given /^I visit the app$/ do
  visit '/'
end

and run Cucumber again:

$ bundle exec cucumber
Using the default profile...
Feature: hello world
  I want the application to greet the world every time I use it

  Scenario: greet the world        # features/hello.feature:4
    Given I visit the app          # features/step_definitions/hello_steps.rb:1
    Then it should greet the world # features/step_definitions/hello_steps.rb:5
      TODO (Cucumber::Pending)
      ./features/step_definitions/hello_steps.rb:6:in '/^it should greet the world$/'
      features/hello.feature:6:in 'Then it should greet the world'

1 scenario (1 pending)
2 steps (1 pending, 1 passed)
0m0.367s

And the first step in the scenario passes, but the second step is still pending. So I modify it:

Then /^it should greet the world$/ do
  page.should have_content("Hello world")
end

then I run Cucumber again:

$ bundle exec cucumber
Using the default profile...
Feature: hello world
  I want the application to greet the world every time I use it

  Scenario: greet the world        # features/hello.feature:4
    Given I visit the app          # features/step_definitions/hello_steps.rb:1
    Then it should greet the world # features/step_definitions/hello_steps.rb:5
      undefined local variable or method 'response_body' for #<Object:0x9741db0> (NameError)
      ./features/step_definitions/hello_steps.rb:6:in '/^it should greet the world$/'
      features/hello.feature:6:in `Then it should greet the world'

Failing Scenarios:
cucumber features/hello.feature:4 # Scenario: greet the world

1 scenario (1 failed)
2 steps (1 failed, 1 passed)
0m0.205s

Which is understandable, as I haven’t created anything that can render such greeting.

To rectify that I run:

$ padrino gen controller events index

which in turn creates events.rb under app/controllers and events_controller_spec.rb under spec/app/controllers/.

If you open spec/app/controllers/events_controller_spec.rb you’ll see a default example:

require 'spec_helper'

describe "EventsController" do
  before do
    get "/"
  end

it "returns hello world" do
    last_response.body.should == "Hello World"
  end
end

which coincidentally is exactly what I’m trying to describe.

When I run the spec, like so:

$ padrino rake spec

it will fail. This is to be expected as I still don’t have anything written yet. To remedy that, I add an index route to the Event controller in app/controllers/events.rb:

Rubytoday.controllers :events do
  get :index, :map => "/" do
    "Hello world"
  end
end

To verify the route I can do it via a Rake’s task:

$ padrino rake routes
=> Executing Rake routes ...

Application: Rubytoday
    URL                  REQUEST  PATH
    (:events, :index)      GET    /

Which confirms that I have set up the default root path to Events controller’s index method.

Then to verify it via RSpec:

$ padrino rake spec
=> Executing Rake spec ...

EventsController
  returns hello world (FAILED - 1)

Failures:

1) EventsController returns hello world
     Failure/Error: last_response.body.should == "Hello World"
       expected: "Hello World"
            got: "Hello world" (using ==)
     # ./spec/app/controllers/events_controller_spec.rb:9:in `block (2 levels) in <top (required)>'

Finished in 0.28037 seconds
1 example, 1 failure

Failed examples:

rspec ./spec/app/controllers/events<em>controller</em>spec.rb:8 # EventsController returns hello world
rake aborted!
ruby -S bundle exec rspec -fs --color ./spec/app/controllers/events<em>controller</em>spec.rb failed

Tasks: TOP => spec => spec:app
(See full trace by running task with --trace)

Which fails, but if you look at the example, the only difference is that the example expected the output to be “Hello World” as opposed to “Hello world”. The error is in the spelling. Once I change that…

$ padrino rake spec
=> Executing Rake spec ...

<p>EventsController
  returns hello world

<p>Finished in 0.22763 seconds
1 example, 0 failures

The example passes. Then to verify that Cucumber is passing:

$ bundle exec cucumber
Using the default profile...

<p>Feature: hello world
  I want the application to greet the world every time I use it

Scenario: greet the world        # features/hello.feature:4
    Given I visit the app          # features/step<em>definitions/hello</em>steps.rb:1
    Then it should greet the world # features/step<em>definitions/hello</em>steps.rb:5

1 scenario (1 passed)
2 steps (2 passed)
0m0.402s

I should also check the output for myself, by starting the app:

$ padrino start
=> Padrino/0.10.2 has taken the stage development at http://0.0.0.0:3000
[2011-10-03 00:51:01] INFO  WEBrick 1.3.1
[2011-10-03 00:51:01] INFO  ruby 1.9.2 (2010-08-18) [i686-linux]
[2011-10-03 00:51:01] INFO  WEBrick::HTTPServer#start: pid=16767 port=3000

and opening the URL in the browser: http://0.0.0.0:3000.

Hello wold screenshot

Hello wold screenshot

This completes the desired output and demonstrates the BDD flow:

Cucumber –> RSpec –> Code

First I wrote the feature and described the behaviour, then I wrote the example in RSpec and finally I implemented the code to fit both the feature and the example.

Now for the final step, to commit it to Git:

$ git status
# On branch hello_world
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   deleted:    features/add.feature
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   app/controllers/
#   app/helpers/
#   features/hello.feature
#   features/step_definitions/hello_steps.rb
#   spec/app/
no changes added to commit (use "git add" and/or "git commit -a")

This lists all the new content that needs to be committed to Git along with the deleted content: features/add.feature file, which I removed earlier.

$ git add .
$ git status
# On branch hello_world
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
#  new file:   app/controllers/events.rb
#  new file:   app/helpers/events_helper.rb
#  new file:   features/hello.feature
#  new file:   features/step<em>definitions/hello</em>steps.rb
#  new file:   spec/app/controllers/events<em>controller</em>spec.rb
#
# Changed but not updated:
#  (use "git add/rm <file>..." to update what will be committed)
#  (use "git checkout -- <file>..." to discard changes in working directory)
#
#     deleted:    features/add.feature
#

I stage the new content and then I add it:

$ git commit -m "Implemented the 'Hello world' example"
[hello_world de9a0d4] Implemented the 'Hello world' example
 5 files changed, 58 insertions(+), 0 deletions(-)
 create mode 100644 app/controllers/events.rb
 create mode 100644 app/helpers/events_helper.rb
 create mode 100644 features/hello.feature
 create mode 100644 features/step_definitions/hello_steps.rb
 create mode 100644 spec/app/controllers/events_controller_spec.rb

This concludes the first tutorial in the series. In the next tutorial I will show how to merge content in Git,  how to deploy it to Heroku and I’ll carry on building the rest of the app. If this is your first look at Padrino, I’d love to hear what you think in the comments below. All the code can be obtained from here: https://github.com/RubySource/simic_padrino.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://padrinorb.com Nathan Esquenazi

    Great tutorial so far, thanks for putting this together and look forward to seeing the other parts in the series.

  • http://github.com/joshbuddy Joshua Hull

    Wow, thank you so much for putting this together! Amazing!

  • http://www.mxicoders.com website development

    There are the nice information you have provided. I think this is a great blog for developers.

  • Aleksandar Simic

    Hello,

    thank you all for you words of encouragement :)

  • Ratko

    Svaka čast, majstore. Ovaj tekst će mi značajno pomoći u daljem radu. Nastavite samo tako. Postajem Vaš verni pratilac na blogu i tviteru. Pozdrav iz Djurakovca