Posts Tagged ‘cucumber’

Getting a project off the ground with TDD and Cucumber

Sunday, June 21st, 2009

RSimpy

I am currently wrapping up an initial release of a new gem that wraps the Simpy.com API. I like social bookmarking and used delicious for years before switching to Simpy as my primary service. Simpy is not the biggest online bookmarking service, but I’ve decided to use it for reasons I will outline in another post. In any case I’ve put together a gem that wraps the API with the help of Httparty. It actually turned out much simpler than I expected it to be, httparty did all of the heavy lifting, but again that is for another post.

So, I have a functioning gem for Simpy, now I want to make something useful with it. The reason I made the gem was to simplify my bookmarking which is overall a mess on multiple workstations in multiple locations. Since I’ve recently been getting familia with cron, I’ve decided to make a script to store urls locally and use cron to run a script that pushes a file of pages to Simpy.

As I touched on briefly, I’ve been unhappy with all my previous bookmarking solutions thus far. This time I want to make it, dead simple. I don’t want to have to enter any additional information if I don’t want to, just a url and have it go on it’s merry way. However to post the link, I will need to at least have the page title. Pulling a page title from a url is really outside of the scope of RSimpy. Ruby makes it fairly simple to do, but I don’t want to isolate the functionality to a script. Ruminate to the rescue, but what is Ruminate?

Ruminate

Well the answer to that question is that I’m not really sure. A few months back, I made a gem with jeweler called ruminate with the description “Extracts statistics from html documents”. Actually, I’m not really sure what I originally had in mind when I setup this gem, but it will be useful now since I want to extract at least the page title. Once I get the ability to extract a page title, I can make a script that is simply glue code to put Ruminate together with RSimpy and voila!

Bootstrapping the project

I have a vague idea of something that I want Ruminate to do, that is I want to be able to query a url to return the title of the page. It’s obviously not the entire purpose of the gem, but I’m sure that will be lots of other bits that will come together in time.

The question is, How do I kickstart this project with TDD to get it off the ground?

Programming by Intention

Programming by intention is the practice of writing code, while pretending that any methods or classes you might reference already exist and operate in the way that you reference them. It’s a great way of uncovering a simple intuative API that you might otherwise would have missed.

I’ve seen it described in two books, first in Everyday Scripting with Ruby, from The Pragmatic Bookshelf. Secondly, it’s used in an excellent book I’m currently reading called Test Driven from Manning.

There are a number of sources stating that TDD is about design first and automated testing is a nice by-product. In my limited experience with TDD, programming by intention is the crux of this design, and by extension the primary purpose of TDD.

Cucumber

One of the things that I really like about cucumber is that you can plainly state your intent and important bits of your feature focused domain in plain English and match that up to executable code. To describe the ability of Ruminate to grab the title of a url, I made the following test:

Feature: Get Page Title
  In to get the page title
  A user of ruminate
  Will request a page title
 
  Scenario: Requesting the title of a page from the page
    Given the url "http://www.google.com"
    When I execute the request
    Then the "title" should be "Google"

I let cucumber auto-generate the regular expression blocks and came up with the following steps:

 
Given /^the url "([^\"]*)"$/ do |url|
  @url = url
end
 
When /^I execute the request$/ do
  query = "Select title from #{@url};"
  @result = chew query
end
 
Then /^the "([^\"]*)" should be "([^\"]*)"$/ do |msg, value|
  assert_equal value, @result
end

As you can see I’ve decided to try out a sql like syntax for the tool. I thought it sounded interesting, we’ll see if I keep it. In any case, I now need to make this test pass with the help of a mixin, that I’m including in features/support/env.rb

# features/support/env.rb
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
require 'ruminate'
include Ruminate
 
require 'test/unit/assertions'
 
World(Test::Unit::Assertions)
# lib/ruminate.rb
require 'query_parser'
require 'engine'
 
module Ruminate
  def chew query
    parser = Ruminate::QueryParser.new
    query_object = parser.parse query
    engine = Ruminate::Engine.new
 
    engine.execute query_object
  end
end

Here I’ve used the mixin to split the method into to operations and encapsulated each of these into a class. Programming by intention has now generated a mixin and two classes that have a clean separation of responsibilities. Here is the simplest thing that could possibly work to make the feature pass.

# lib/query_parser.rb
module Ruminate
  class QueryParser
    def parse query
      nil
    end
  end
end
# lib/engine.rb
module Ruminate
  class Engine
    def execute query
      "Google"
    end
  end
end

Obviously this isn’t the final code, but it gives us the minimal implementation to pass the feature. Now I can turn to Test::Unit and shoulda to flesh out tests and implementations to make it work in a more useful manner.

What I really wanted to show here is how to get a TDD project started. I know that I’ve struggled with this in the past. What to test first can be a big decision, one that can create a type of code writers block. When this moment happened where everything just came together and I was able to generate a mixin and two orthagonal classes with a simple feature, I wanted to write it up.

The Code

The full code for this example can be found on github with the tag simplest-thing-that-could-possibly-work

Making your Rails controllers respond

Friday, May 8th, 2009

I was looking at Google Analytics today and found that the following searhes often end up here:

webrat no action responded to /. actions: index

and

"no action responded to" webrat

It’s good to see that people are trying out webrat, presumably with cucumber to test their apps.

The problem here is most likely that you need to add a method to your controller to handle the incoming request.  So if you are getting the message

no action responded to new

you would want to go to the controller that you are testing and add something like this:

1
2
3
def new
    #TODO make new do something
end

Don’t forget to add a view such as app/views/example/new.html.erb that corresponds to the action you just added to your controller.

Also, learn to find the TODO annotation again with rake.

Cucumber lost the letter a on windows

Tuesday, May 5th, 2009

Find that the letter ‘a’ is missing from your output from cucumber in windows? Try adding the line

$KCODE=''

to your features/support/env.rb file.

It worked for me.

Listing of cucumber's out-of-the-box webrat steps

Sunday, April 19th, 2009

Commonly used webrat steps found in cucumber

(replace words in [brackets] with the approprate word.)

Given

Given I am on [page_name]

When

When I go to [page_name]
When I press "[button]"
When I follow "[link]"
When I fill in "[field]" with "[value]"
When I select "[value]" from "[field]"
When I select "[time]" as the date and time				#datetime_select
When I select "[datetime]" as the "[datetime_label]" date and time	#specific datetime_select
When I select "[time]" as the time			#time select
When I select "[time]" as the "[time_label]" time	#specific time select
When I select "[date]" as the date			#date_select
When I select "[date]" as the "[date_label]" date	#specific date_select
When I check "[field]"
When I uncheck "[field]"
When I choose "[field]"
When I attach the file at "[path]" to "[field]"

Then

Then I should see "[text]"
Then I should not see "[text]"
Then the "[field]" field should contain "[value]"
Then the "[field]" field should not contain "[value]"
Then the "[label]" checkbox should be checked
Then I should be on [page_name]

Chores: a test driven website – part 6 (the son of)

Saturday, January 24th, 2009

This is part of the Chores series of posts

Last time we found the warm fuzzy feeling of green tests and a completed feature.   Unfortunately, while that is a great feeling, our website is far from complete.  We can however begin to leverage what we have learned so far to get this project moving.  I write up my next feature for cucumber and save it in the features directory as define_children.feature. 

Story:  Define child
As a parent
I want to define my child(ren)
So that I can assign them chores

Scenario: Defining a child
Given I am on the homepage
When I follow "Add Child"
And I fill in "child[nickname]" with "Bobby"
And I fill in "child[open_identifier]" with "bobby.example.com"
And I press "Add"
Then I should see "Child added."
And I should see "Bobby"

Scenario: Defining a second child
Given I am on the homepage
When I follow "Add Child"
And I fill in "child[nickname]" with "Bobby"
And I fill in "child[open_identifier]" with "bobby.example.com"
And I press "Add"
And I follow "Add another Child"
And I fill in "child[nickname]" with "Suzy"
And I fill in "child[open_identifier]" with "suzy.example.com"
And I press "Add"
Then I should see "Child added."
And I should see "Bobby"
And I should see "Suzy" 

Here are a few things that you can pick out of this feature.

  • It infers  that there is some sort of identity, probably with some sort of authentication at a future point.
  • Identities will likely have roles of parent and child.
  • The fact that the child is associated with an openID confirms that there will be authentication at some point.
  • Identites have either a one-to-many or a many-to-many relationship.

I think (at this point) that I’m going to leave the actual authentication out of the scope of the feature.  However it looks like there will be an Identity and some sort of relationship model in our near future.  Notice also that I took advantage of some of webrat’s built in step defintions.  I considered making a custom step to combine the entering of nickname, openId and pressing add, but I left it with the defaults for now.

Before we go ahead and create the models, let’s follow our feature and see where it leads us.  Cucumber’s output looks like:

$ cucumber features
Story:  Define child  # features/define_children.feature
As a parent
I want to define my child(ren)
So that I can assign them chores
  Scenario: Defining a child      # features/define_children.feature:6
    Given I am on the homepage   # features/step_definitions/chores_steps.rb:1
    When I follow "Add Child"      # features/step_definitions/webrat_steps.rb:8
      Could not find link with text "Add Child" (RuntimeError)
      c:/Ruby/lib/ruby/gems/1.8/gems/webrat-0.3.4/lib/webrat/core/flunk.rb:4:in 'flunk'

So we will need to modify the homepage to have a link to add child and to do that we will need to have a url to go to.   That means we need a controller.  Since the link we are making is to add a child let’s call it the Children Controller. 

$ ruby script/generate controller Children index new create
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/children
      exists  test/functional/
      create  app/controllers/children_controller.rb
      create  test/functional/children_controller_test.rb
      create  app/helpers/children_helper.rb
      create  app/views/children/index.html.erb
      create  app/views/children/new.html.erb
      create  app/views/children/create.html.erb

and update ./config/routes.rb to read

1
2
3
4
5
ActionController::Routing::Routes.draw do |map|
  map.resources :chores
  map.resources :children
  map.root :controller => 'home'
end

and finally update our homepage view (./app/views/home/index.html.erb) to read

views

1
2
<%= link_to "Add Chore", new_chore_path %>
<%= link_to "Add Child", new_child_path %>

Let’s check back with Cucumber and see how we did.

$ cucumber features
Story:  Define child  # features/define_children.feature
As a parent
I want to define my child(ren)
So that I can assign them chores
  Scenario: Defining a child      # features/define_children.feature:6
    Given I am on the homepage      # features/step_definitions/chores_steps.rb:1
    When I follow "Add Child"      # features/step_definitions/webrat_steps.rb:8
    And I fill in "child[nickname]" with "Bobby"      # features/step_definitions/webrat_steps.rb:12
      Could not find field labeled "child[nickname]" (RuntimeError)
      c:/Ruby/lib/ruby/gems/1.8/gems/webrat-0.3.4/lib/webrat/core/flunk.rb:4:in 'flunk'

Not bad at all the step passed and it is now looking for the form on the new view.   Let’s make it by editing /app/views/children/new.html.erb to read:

1
2
3
4
5
6
7
8
9
10
11
12
13
<% form_for(@child) do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :nickname %><br />
    <%= f.text_field :nickname %>
  </p>
 
  <p>
    <%= f.label :open_identifier %><br />
    <%= f.text_field :open_identifier %>
  </p>
  <%= f.submit "Add" %>
<% end %>

Let’s also remove ./app/views/children/create.html.erb file while we are at it, since we won’t need a view for this method.

Hmmm. hold on for a second. We are referencing an instance of @child that doesn’t exist. That’s definately going to fail. We need to add a model to define relationships between identities not to mention the identities themselves.

$ ruby script/generate model Identity identifier:string
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/identity.rb
      create  test/unit/identity_test.rb
      create  test/fixtures/identities.yml
      exists  db/migrate
      create  db/migrate/20090124055031_create_identities.rb

and

$ ruby script/generate model Child parent_id:integer child_id:integer
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/child.rb
      create  test/unit/child_test.rb
      create  test/fixtures/children.yml
      exists  db/migrate
      create  db/migrate/20090124055548_create_children.rb

When we return it will be time to edit the migrations and run them and then dive back into Test::Unit.