An Easy 5 Step Process for Test Driven Development

by woody2shoes on July 20, 2009

A lot of people try Test Driven Development by writing the test, writing the code, and then wondering what the big deal is. Here’s the process I follow along with an explanation of why each step helps.

As a quick note, all tests are written with the RSpec framework.

1. Write the test

Let’s say that I need to import a csv file containing a list of zip codes into a table on my Ruby on Rails application. I’m going to ignore the UI or view for uploading the file for right now and concentrate on the logic that opens the file and imports the zip codes. I’ve decided that I want this functionality on my ZipCode model. So, I open up my RSpec specification for ZipCode’s and add the test.

vim spec/models/zip_codes_spec.rb

Here’s the test. This assumes a file with 100 new zip codes.

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe ZipCode do
  it "should import the zip codes from a file" do
    count = ZipCode.count
    ZipCode.import_from_csv(File.dirname(__FILE__) + '/../fixtures/zips.csv')
    ZipCode.count.should == count + 100
  end
end

2. Outline the method using comments.

Here’s a quick example, then I’ll explain why I do this.

class ZipCode
  def self.import_from_csv(filename)
    # open csv file
    # take each row and ...
      # create a new zip code from it
    # close csv file
  end
end

The primary thing I get from this is that I can see what the code will look like and I can determine which parts of my code I need to break out into new methods. Of course, new methods get their own tests, hence step 3.

3. Write tests for any supporting methods

In this case, I’m probably going to use the Fastercsv library for Ruby, but for the sake of argument, let’s say I have to write my own method to create a ZipCode from a row from my CSV file.

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe ZipCode do
  it "should import the zip codes from a file" do
    count = ZipCode.count
    ZipCode.import_from_csv(File.dirname(__FILE__) + '/../fixtures/zips.csv')
    ZipCode.count.should == count + 100
  end

  it "should create a new zip code from a row of a csv file" do
    row = ['84043', 'Lehi', 'UT']
    ZipCode.create_from_csv_row(row)
    ZipCode.find_by_zip_and_city_and_state("84043", "Lehi", "UT").should_not be_nil
  end
end

4. Run the test.

Running the test does one important thing for you. It insures that there isn’t some unexpected behavior in your code that gives you a false positive. Once you see each of your tests fail, you’re ready to go.

5. Write the code.

For me this is the best part of the challenge. I love writing code. Tests are just the insurance for me that I’m writing good code. As before stated, I’m going to write this using the Fastercsv Ruby library.

class ZipCode
  def self.import_from_csv(filename)
    # open csv file
    # take each row and ...
    FasterCSV.foreach(filename) do |row|
      # create a new zip code from it
      create_from_csv_row(row)
    end
    # close csv file
  end

  def self.create_from_csv_row(row)
    create(:zip => row[0], :city => row[1], :state => row[2])
  end
end

6. Refactor if necessary

The 6th step is one that is frequently, but not always used. Sometimes we write messy code to get the job done. That’s really much more acceptable with Test Driven Development because once we get the test to pass, we can go back and change the code. The test tells us if we’ve broken something while refactoring and we’ll know when our pretty code actually works.

Previous post:

Next post: