Posts Tagged ‘TDD’

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

Functional Testing Rails with a Session variable

Wednesday, April 8th, 2009

I recently had a frustrating situation while using the session to store the id of the current user. I was writing functional tests for my app an was using code based on the acts_as_authenticated plugin. My problem was that I was trying to set the id in the session before I made the get request and for whatever reason it wasn’t working and I kept getting redirected to the login screen.

Now I was not using the acts_as_authenticted_plugin, so

1
2
3
  def setup
    login_as :quentin
  end

was not available to me. Instead, I tried doing something along the lines of this, which mimics what it seemed that the authenticated_test_helper was doing:

1
2
3
4
test "that a request for index will be sucessful" do
  @request.session[:identity_id] = 1
  assert_response :success
end

but that didn’t work either. So I looked around a number of places and finally came across the newly shiny Rails Guide for functional testing. There, before my eyes, was the answer.

The get method kicks off the web request and populates the results into the response. It accepts 4 arguments:

  • The action of the controller you are requesting. This can be in the form of a string or a symbol.
  • An optional hash of request parameters to pass into the action (eg. query string parameters or post variables).
  • An optional hash of session variables to pass along with the request.
  • An optional hash of flash values.
  • Four arguments! not only the two I’d used (action and params), but 4. Armed with this knowledge, I soon got my test to pass. Here is a sample passing test

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    def setup
      @identity = Identity.make
    end
     
    test "that a request for index will be sucessful" do
       get :index, nil, build_session_hash_for(@identity)
       assert_response :success
    end
     
    protected
      def build_session_hash_for identity
        {'identity_id' => identity.id}
      end

    Hopefully this will help out someone else who may run into this issue.

    Chores: A test driven website, part 2

    Friday, January 9th, 2009

    This is part of the Chores series of posts

    In which I continue to build the chores website and demonstating how simple it is to use a test first methodology. To follow along, please see my previous post.

    Doh! I forgot to commit!

    When I left off, I had come to a natural stopping point. A place where I was shifting focus from features to unit testing. I like to make my commits when my tests are all passing, however if I’m implementing a new feature, I don’t apply this hueristic since completing a feature will span multiple units of work. So let’s commit this. First let’s see where I am:

    $ git status
    # On branch master
    # Untracked files:
    #   (use "git add <file>..." to include in what will be committed)
    #
    #       features/
    #       lib/
    #       script/cucumber
    nothing added to commit but untracked files present (use "git add" to track)

    Ok, git sees that I’ve added cucumber to my rails app and that I have added a feature. Let’s add these changes to our commit.

    $ git add .

    and commit them

    $ git commit -m "added feature to create chores"

    and finally push a copy out to the repository I set up on my shared host.

    $ git push origin master

    there all set.

    Routing to home

    At this point I’m a little confused as to how to proceed test first. We have a failing feature. When I have a failing feature I like to start by making a test to simulate the same error. However in this case, that is difficult at best, and the failing feature has already told me what I need to do, which is create a home_controller

    $ ruby script/generate controller Home
          exists  app/controllers/
          exists  app/helpers/
          create  app/views/home
          exists  test/functional/
          create  app/controllers/home_controller.rb
          create  test/functional/home_controller_test.rb
          create  app/helpers/home_helper.rb

    .. and map a route to it. To do that I open up my ./config/routes.rb file and add the following route. I like to get rid of the legacy routes so I’ll replace the entire text of the file with the following

    [sourcecode language="ruby"]
    ActionController::Routing::Routes.draw do |map|
    map.root :controller => ‘home’
    end

    So what are these controllers and routes?

    If you are new to Rails, you are probably wondering what we just did. The controller is the C in the MVC pattern that Rails uses. It's role is to handle web serving of the application. It's kind of like an office assistant for your application. It be able to handle frequent requests and delegate them to the proper subject matter expert. When a request comes in for a list of Widgets, it will go to the Widget model and ask it for a list of widgets and then pass that off to the user interface (view) to display in the browser. When the user interface passes it a bag of information and asks it to save the information, the controller will create an instance of the appropriate model from the bag of information and then tell the model it should save the information.

    Controllers really shouldn't become too involved in the details, but should delegate well to the objects that know what they are doing. Rails have a nice script to generate controllers, which we used above. In this case we generated a home controller which we will use to handle requests for the application's home page or root.

    The second thing we did is define a route. Routes are related to controllers, in that they show the framework how to translate a request such as http://www.exmaple.com/sprocket/new into a method call on a controller. In the case of the above url, it would call the method new on the sprocket controller. You can find your routes in config/routes.rb and view all of your current routes with the rake task

    rake routes

    On with the show!

    Now let's go back to cucumber and see where we are.

    $ cucumber features
    Story:  Define chores  # features/define_chores.feature
    As a parent
    I want to define chores
    So that I can assign them to my children
      Scenario: Creating a chore       # features/define_chores.feature:6
        Given I am on the homepage    # features/step_definitions/chores_steps.rb:1
          No action responded to index. Actions:  (ActionController::UnknownAction)

    Ok, our route is now wired up nicely to the controller, but the home controller does not respond to index. Lets add this to the controller

    [sourcecode language="ruby"]
    class HomeController < ApplicationController
    def index

    end
    end

    and try cucumber again

    $ cucumber features
    Story:  Define chores  # features/define_chores.feature
    As a parent
    I want to define chores
    So that I can assign them to my children
      Scenario: Creating a chore     # features/define_chores.feature:6
        Given I am on the homepage   # features/step_definitions/chores_steps.rb:1
          Missing template home/index.erb in view path c:/rails/chores/app/views: (ActionView::MissingTemplate)

    Ah ha! We are making progress. Our request has passed through the controller and is now looking for a view to render. One thing that I would like to point out is that doing the development in this way really lays out how the pieces of Rails all fit together. Something that we wouldn't have seen if we had chosen to generate a scaffold or we built it piecemeal without the tests metering our pace.

    Views

    Views are what end up being displayed in the web browser, they are a sort of template. By default, erb will process the template in the context of your request and output the dynamic content. Pretty cool stuff, and it gives you great control over your output.

    Well what are we waiting for, let's make a view.

    $ touch app/views/home/index.html.erb

    In windows you can use your IDE or folder browser to create the file.

    $ cucumber features
    Story:  Define chores  # features/define_chores.feature
    Story:  Define chores
    As a parent
    I want to define chores
    So that I can assign them to my children
     
      Scenario: Creating a chore                          # features/define_chores.feature:6
        Given I am on the homepage                        # features/step_definitions/webrat_steps.rb:6
        When I follow "Add Chore"                         # features/step_definitions/webrat_steps.rb:18
          Could not find link with text or title or id "Add Chore" (Webrat::NotFoundError)
          (eval):2:in '/^I follow "([^"]*)"$/'
          features/define_chores.feature:8:in 'When I follow "Add Chore"'
        And I fill in "chore[name]" with "My first chore" # features/step_definitions/webrat_steps.rb:22
        And I press "Add"                                 # features/step_definitions/webrat_steps.rb:14
        Then I should see "Chore added."                  # features/step_definitions/webrat_steps.rb:93
        And I should see "My first chore"                 # features/step_definitions/webrat_steps.rb:93
     
    1 scenario
    1 failed step
    4 skipped steps
    1 passed step
    rake aborted!

    Hey look! Our first step has passed! Let's give ourselves a pat on the back. We have a website with a dynamic (albeit blank) homepage, that is tested.

    Not all quotes are equal

    I had an issue with my feature where the double quotes in my feature were translated automatically to open and closed style quotes. This was causing my steps not to match. If you find that you have a bunch of steps that are undefined at this point, that may be the issue.

    Catch the passing step fever

    The next step is one that is generated by webrat. It is looking for a link to follow called "Add Chore". The problem is that we didn't actually add the link, or anything else to the view. So let's update the view to read

    [sourcecode language="ruby"]
    <%= link_to "Add Chore", chore_path %>

    and fire up cucumber

    $ cucumber features
    Story:  Define chores  # features/define_chores.feature
    As a parent
    I want to define chores
    So that I can assign them to my children
      Scenario: Creating a chore      # features/define_chores.feature:6
        Given I am on the homepage    # features/step_definitions/chores_steps.rb:1
          undefined local variable or method 'chore_path' for #<actionView::Base:0x4baf67c> (ActionView::TemplateError)
          On line #1 of app/views/home/index.html.erb
     
          1: <%= link_to "Add Chore", chore_path %>
     
          app/views/home/index.html.erb:1

    ok, we need to define resource for this, let's add a route and controller.

    [sourcecode language="ruby"]
    ActionController::Routing::Routes.draw do |map|
    map.resources :chores
    map.root :controller => 'home'
    end

    and

    $ ruby script/generate controller Chores
          exists  app/controllers/
          exists  app/helpers/
          create  app/views/chores
          exists  test/functional/
          create  app/controllers/chores_controller.rb
          create  test/functional/chores_controller_test.rb
          create  app/helpers/chores_helper.rb
    $ cucumber features
    Story:  Define chores  # features/define_chores.feature
    As a parent
    I want to define chores
    So that I can assign them to my children
      Scenario: Creating a chore    # features/define_chores.feature:6
        Given I am on the homepage   # features/step_definitions/chores_steps.rb:1
          chore_url failed to generate from {:action=>"show", :controller=>"chores"}
     - you may have ambiguous routes, or you may need to supply additional parameter
    s for this route.  content_url has the following required parameters: ["chores",
     :id] - are they all satisfied? (ActionView::TemplateError)
          On line #1 of app/views/home/index.html.erb
     
          1: <%= link_to "Add Chore", chore_path %>

    Dang it! still getting an error. Hmmm.. wait a minute. chore_path is generating a show action. That's not what we want to do. We want a new action to be RESTful or maybe an index action if we don't see REST in the future for this app. A quick consultation with the domain expert (me) says that there is no valid reason not follow the RESTful guidelines at this point so let's fix the home/index view to read.

    [sourcecode language="ruby"]
    <%= link_to "Add Chore", new_chore_path %>

    cucumber says...

    $ cucumber features
    Story:  Define chores  # features/define_chores.feature
    As a parent
    I want to define chores
    So that I can assign them to my children
      Scenario: Creating a chore   # features/define_chores.feature:6
        Given I am on the homepage   # features/step_definitions/chores_steps.rb:1
        When I follow "Add Chore"   # features/step_definitions/webrat_steps.rb:8
          No action responded to new. Actions:  (ActionController::UnknownAction)

    Great! That fixed it. Now we need to make sure there is a new action on the chores controller.

    class ChoresController < ApplicationController
      def new
     
      end
    end
    $ cucumber features
    Story:  Define chores  # features/define_chores.feature
    As a parent
    I want to define chores
    So that I can assign them to my children
      Scenario: Creating a chore    # features/define_chores.feature:6
        Given I am on the homepage    # features/step_definitions/chores_steps.rb:1
        When I follow "Add Chore"      # features/step_definitions/webrat_steps.rb:8
          Missing template chores/new.erb in view path c:/rails/chores/app/views: (ActionView::MissingTemplate)

    cucumber likes it! But we still have an error. Let's create ./app/views/chores/new.html.erb as:

    <% form_for(@chore) do |f| %>
      <%= f.error_messages %>
     
      <p>
        <%= f.label :name %><br />
        <%= f.text_field :name %>
      </p>
     
      <%= f.submit "Add" %>
      </p>
    <% end %>

    Hmmm.. wait we are getting ahead of ourselves. We need a model to be able to pass it to our view. We will have to generate that next.

    Well we didn't get to Test::Unit this time around, but with a model in sight it's sure to be right around the corner. Until next time..

    Chores: A test driven website

    Thursday, January 8th, 2009

    This is part of the Chores series of posts

    I admit it, I don’t always write my tests first, and at other times I don’t write my tests at all. There I said it. However, I have found that writing tests first has accelerated the speed at which I learn and it has improved the quality of my work.

    There is a general feeling that people who use TDD see themselves as better than those who don’t use it. I don’t think that this is the case. I think that this happens because once you start working in these styles, it’s strange to work without them. It’s not that you can’t or won’t do it, but it feels like you are working with a reduced toolset when you do.

    I’m no expert at test driven development or similar styles, but the more I use them, the harder it is to imagine not being able to design and shape my objects with automated tests. I wouldn’t want to figure out how related objects fit together without the documentation that automated testing provides.

    The term test driven fools people into thinking that it’s all about testing, but it’s not. It’s about creating functional designs with reduced dependencies. It’s also about being able to maintain your code. It’s about self improvement.

    The point of learning to work in these styles is not to be better than those that don’t. Instead, the point is to make the code you wrote today better than the code you wrote a week ago, or a month ago. It is also so that when you or someone else looks at the code you wrote today, they will find it to be:

    • working
    • documented
    • flexable
    • extendable

    There are many articles on the benefits of working in this style, and my point is not to repeat them. In fact my purpose is to prove them correct, or at least to show that it is not difficult to do and just takes a little discipline. The same discipline that I lack at times, but I’m getting better.

    The Project

    I am the father of 4 kids, and they are wonderful children who make messes and generally don’t clean up after themselves unless told to do so (and sometimes not even then). My general idea of a website is to built a Rails app that a family can go and if they are parents, keep track of what their kids do or don’t do. If they are kids, they can document what they have done and perhaps see much they have earned for the week in allowance. I have tried doing allowances before, but the problem is that my kids have to be told to do what they are supposed to be doing quite often to make sure they are doing it. I don’t want to do this. I want to provide immediate feedback for them on what they are earning, and I am hoping that this will give them the motivation they need to keep going. I also want to help them to develop the habits that will continue with or without the draw of rewards. It’s a moderately involved project,

    Test or Die

    Because January is Test Awareness Month I have decided to document bulding (at least beginning to build) this site on my blog using Cucumber and Test::Unit to drive it. I am developing currently on a Windows machine, though this may change as the project goes on as my other machines are running Ubuntu. So without further ado…

    Note: (May 11, 2009 – I originally created this with Rails v2.2.2. I’m currently running back through to check compatability with Rails v2.3.2)

    Start!

    $ rails chores
          create
          create  app/controllers
          create  app/helpers
          .....
          create  log/development.log
          create  log/test.log

    I have my rails app. The first thing I want to do is get this into source control. In my case git.

    $ cd chores

    and

    $ git init

    now I don’t want to store everything in source control. To to make git ignore files and or paths I create a .gitignore file in the chores directory and it looks like this…

    tmp*
    log*
    *[~#]

    what this does is it ignores the tmp and log directories and it also ignores any backup files which contain ~ or #.

    Now I can check the status to make sure that the .gitignore file is working.

    $ git status
    # On branch master
    #
    # Initial commit
    #
    # Untracked files:
    #   (use "git add <file>..." to include in what will be committed)
    #
    #       .gitignore
    #       README
    #       Rakefile
    #       app/
    #       config/
    #       doc/
    #       public/
    #       script/
    #       test/
    nothing added to commit but untracked files present (use "git add" to track)

    Followed by

    $ git add .
     
    $ git commit -am "initial commit"

    Which adds my site to git’s index and commits my first commit. Now I’m going to backup my git repository to my shared host. A good explanation of this can be found here.

    Now I already have cucumber, webrat and rspec installed as gems. In order to drive my development with cucumber features I need to set up my app to use cucumber.

    $ ruby script/generate cucumber
          create  features/step_definitions
          create  features/step_definitions/webrat_steps.rb
          create  features/support
          create  features/support/env.rb
          create  features/support/paths.rb
          exists  lib/tasks
          create  lib/tasks/cucumber.rake
          create  script/cucumber

    That does the trick.

    Gotchas

    I’ve found a couple gotchas at this point when working in Windows. The first is that cucumber doesn’t display the letter ‘a’ in the output if you don’t modify ./features/support/env.rb with the following line:

    1
    
    $KCODE=''

    also you will want to make sure you are using a version of the sqlite gem that supports windows:

    gem install sqlite3-ruby -v=1.2.3

    What are your features?

    Cucumber Features are a nice way to be able to document what you want your site to do and verify that it does it. Probably the easiest way do show this is to do it. I like to make features that aren’t too broad or two specific. I will start with one that is core functionality and save it in the ./features directory as define_chores.feature

    Story:  Define chores
    As a parent
    I want to define chores
    So that I can assign them to my children
     
    Scenario: Creating a chore
    Given I am on the homepage
    When I follow “Add Chore”
    And I fill in "chore[name]" with “My first chore”
    And I press "Add"
    Then I should see “Chore added.”
    And I should see “My first chore”

    Now we have written our first feature. It is a pretty simple one, but as you can see it describes our intent in readable English. Now let’s fire up cucumber and see what we get.

    Update: The following issue was fixed when I tried it in Rails 2.3.2 and cucumber 0.3.1.

    $ cucumber features
    c:/Ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require': no such file to load -- webrat/rspec-rails (MissingSourceFile)

    Hmmm.. what happened here. Cucumber uses the ./features/support/env.rb file for configuration let’s look there. Sure enough there is a require statement for ‘webrat/rspec-rails’. Let’s google it. Looks like the offending require is to add some webrat matchers to rspec. I’m just going to comment it out for now and try again.

    Update: Using the features rake task (rake features from the command line) at this point in Rails 2.3.2 and cucumber 0.3.1 caused the following error:

    rake aborted!
    no such file to load -- /home/rubyyot/working/chores/db/schema.rb

    This is because we have not yet defined any database tables. To fix this we simply have to create an empty file in the correct location. In Windows you can navigate to the folder and create a new file. In Linux you can type from your rails root:

    touch db/schema.rb

    This time I get:

    $ rake features
    Story:  Define chores
    As a parent
    I want to define chores
    So that I can assign them to my children
     
      Scenario: Creating a chore                          # features/define_chores.feature:6
        Given I am on the homepage                        # features/step_definitions/webrat_steps.rb:6
          No route matches "/" with {:method=>:get} (ActionController::RoutingError)
          (eval):2:in '/^I am on (.+)$/'
          features/define_chores.feature:7:in 'Given I am on the homepage'
        When I follow “Add Chore”                         # features/define_chores.feature:8
        And I fill in "chore[name]" with “My first chore” # features/define_chores.feature:9
        And I press "Add"                                 # features/step_definitions/webrat_steps.rb:14
        Then I should see “Chore added.”                  # features/define_chores.feature:11
        And I should see “My first chore”                 # features/define_chores.feature:12
     
    1 scenario
    1 failed step
    1 skipped step
    4 undefined steps
     
    You can implement step definitions for undefined steps with these snippets:
     
    When /^I follow “Add Chore”$/ do
      pending
    end
     
    When /^I fill in "([^\"]*)" with “My first chore”$/ do |arg1|
      pending
    end
     
    Then /^I should see “Chore added\.”$/ do
      pending
    end
     
    Then /^I should see “My first chore”$/ do
      pending
    end
     
    rake aborted!
    Command failed with status (1): [/usr/bin/ruby1.8 -I "/usr/lib/ruby/gems/1....]
     
    (See full trace by running task with --trace)

    So what happened here?

    Cucumber loads up all the features and all of the steps and then uses regular expressions to match the features to the steps. Then it executes the code associated with each step in order. Notice that it knew what “Given I am on the homepage” meant. That is because Webrat has already pre-loaded some common rails testing steps for us.

    So what error are we having here? It says “No route matches “/” with {:method=>:get}”.

    What are routes?

    Routes in Rails have some pretty nifty tricks up their sleeves. Routes map an incoming request to an action on a controller. They live in config/routes.rb where you will find some good auto-generated documentation on how to configure them. In this case we need to create a route for our home page. This is accomplished by adding

      map.root :controller => 'home'

    somewhere after

    ActionController::Routing::Routes.draw do |map|

    and before

    end

    This tells rails that any requests that come in for the base url should be routed to the HomeController. Next time we can start working on building this object.