I Spec, You Spec, We RSpec!

I love testing, I could hardly wait for this meeting of the Chicago Ruby Group. I was not disappointed. Unit tests are cool, but specs are awesome. Whats the difference you say? I think its a more natural way to write your tests, it makes you think of the behaviour of your object and not "oh gosh, I have to write 3 tests for each of my methods."

For example.. take a queue:

class Queue
  def initialize 
     @thequeue = []
  end
  def empty?
    @thequeue.size == 0
  end
 
  def size
    @thequeue.size
  end

  def enqueue( item )
    @thequeue.unshift(item)
  end

  def dequeue
    @thequeue.shift
  end

If you were designing a class, you might write out the following requirements:

A new queue
- should be empty

A queue with one item
- should be length of one

(ok maybe not, its really obvious but you probably should be that obvious in your tests)

A unit test might look like:

def test_new_queue_is_empty 
   @q = Queue.new
   assert_equal(@q.size, 0)
end

Meh.

Check out a spec test:

context "A new queue" do
  setup do
    @q = Queue.new
  end
  specify "should be empty" do
    @q.should_be_empty
  end
end

Hey man. That's English. Each test is in a context block, with a setup and specify chunk. Look what happens when you run the test:

E:\server\rubytest\queue>spec -f s queue_spec.rb

A new queue
- should be empty

Hey! that looks like your spec! You can actually print this out and hand it to a non programmer and say Hey, this is what my code does. Lets see another one:

context "A queue with one item" do
  setup do
    @q = Queue.new
    @q.enqueue("one item")
  end
  specify "should be length of one" do
    @q.size.should_be 1
  end
end

And the results of both tests:

A new queue
- should be empty

A queue with one item
- should be length of one

You can do more than one specify in a context block. Check this out:

context "A queue with 5 items" do
  setup do
    @q = Queue.new
    1.upto(5) do |t|
      @q.enqueue("item #{t}")
    end
  end
  specify "should be length of five" do
    @q.size.should_be 5
  end
  specify "should be 4 after dequeue" do
     @q.dequeue
     @q.size.should_be 4
  end
  specify "should be 6 after adding another one" do
     @q.enqueue("item 6")
     @q.size.should_be 6
  end
end

The setup is run before each of the specify blocks.

The rspec site has a much more in depth tutorial, including a mock object component. As for integration with Rails, there are rake tasks you can use to run the specs on all your modules and a generator to create the barebones spec shells for your Rails apps.

Check it out, a new way of thinking about testing is a good thing!