The RSpec post (including an important tip on shared example groups)

So in my Rails apps lately I've been using the hell out of some RSpec. I have to say that it's making me a better, more methodical coder. It's not just allowing me to define my app in terms of expected behaviors and providing the well understood regression testing capabilities of any testing framework. It's a whole new way of organizing my approach to programming.

First of all, starting a large application can seem daunting. To paraphrase Rumsfeld, we don't know what we don't know half the time - so many different features, so much complexity in the way domain objects interract, and the coder inevitably drops a ball juggling all this in his head. Add to this uncertainty the inevitable course corrections by the client, and it's no wonder we lose a lot of sleep during the big pushes to get the foundations of our applications written.

By starting out writing specifications - and not as "business analysts" per se, but rather as programmers who are taking a moment to analyze what real world problems we're trying to solve - we give ourselves a sort of technical permission to begin with a 10,000 foot view and build a structured path down to the ground level, step by behavior driven step. The way the application gets used really should only be considered at whatever level of detail makes sense at a particular stage in the development cycle (the insight of the agile school).

Throughout my "spec'ed" apps I have lots of "pending" specification cases. These are just placeholders to allow me to talk about future functionality I don't yet want to implement. It's remarkably freeing to be able to simply record what you want the code to do in plain English, worrying about the implementation later. You're basically giving yourself a "plan of attack" without being slowed down by how the attack will actually take place - remember, we're worried at this point about simply defining what behavior we should expect, not how that behavior will come about (also see the comments - you can simply omit the block altogether).

describe Bulletin, "when being composed" do
  
  it "should be valid with all fields filled in" do
    pending
  end

  it "should require a title" do
    pending
  end
  
  it "should require a body" do
    pending
  end
  
  it "should require an author" do
    pending
  end
  
  it "should require a valid author" do
    pending
  end
end

Once I've worked out a plan about how this code will behave, I can write a test inside each individual specification block. By writing the test first, seeing the test fail, then implementing the code that makes the test pass, I break a big problem into bite size chunks. I'm also being forced to "think ahead" about what I want to accomplish - thereby discovering edge cases that might complicate my original plan of attack. I cannot overstate the utility of thinking about what your code does in terms of tests.

describe Bulletin, "when being composed" do
  
  fixtures :volunteers
  
  before(:each) do
    @bulletin = Bulletin.new( :body => "Random body text",
                              :title => "the title of the post",
                              :author_id => volunteers(:quentin).id)
  end
  
  it "should be valid with all fields filled in" do
    @bulletin.should be_valid
  end

  it "should require a title" do
    @bulletin.title = nil
    @bulletin.should_not be_valid
  end

The nested describe blocks redefined my whole approach to rspec. As you've seen, before blocks allow you do shared setup for a group of tests. Well, you can share these before blocks among several different types of describe blocks, and the description texts just build on one another. This allows you to organize the spec in a readable and DRY form, factoring out mere setup steps to expose the more readable business logic.

  describe "when attempting to edit a comment" do
    
    before(:each) do
      @bulletin = bulletins(:the_first_one)
      @try_to_get_an_edit_form = lambda { get :edit, 
                                              :member_id => @author_of_the_note.member_url, 
                                              :trade_note_id => @the_note.id, 
                                              :id => @comment.id }
    end

    describe "by somebody other than the comment's author or an administrator" do          
      before(:each) do
        login_as(:some_other_guy)
        @try_to_get_an_edit_form
      end
      
      it "should not successfully load the edit page" do 
        response.should_not be_success
      end
      
      it "should display an error message" do
        puts flash.inspect
        flash[:warning].should == "Unauthorized action."
      end
    end
  
    describe "by an administrator" do
      before(:each) do
        login_as(:admin)
        @try_to_get_an_edit_form
      end
...

The next step from that was getting to shared example groups. This feature allows you to define an example group containing specified behavior that can be shared by multiple subsequent specifications. Take the example of a feature that allows certain user roles to edit a post, but not others. Instead of repeating the expectation for each role's specification, you can define a set of expectations that apply to a certain class of behavior:

describe "an allowable edit", :shared => true do

  it "should render the edit template" do
    response.should render_template('edit')
  end
end

Then just reference that each role should behave like that example group:

describe "by an administrator" do
  before(:each) do
    login_as(:admin)
      @try_to_get_an_edit_form
    end
      
  it_should_behave_like "an allowable edit"
end

Now, it took me a long time to get RSpec to play nice with shared example groups, because I had gotten so used to heavily nested describe blocks that I wanted to include the shared example groups in these blocks. That will not work. You need to put all your shared example groups outside of any describe blocks. They also need to be run before they can be used, so I put them at the top

I know there's a way to load shared examples from separate file, allowing you to bounce behaviors throughout your specs, but I haven't gotten there yet (I hope to discover RSpec Stories soon as well, which promise an even more natural language way to specify behavior). Suffice to say, this is power enough for me now. As somebody who gets easily overwhelmed and distracted, the focus that behavior driven development gives me is priceless. I also appreciate being able to have an intermediate layer between the technical implementation frame of mind and the human-useable functionality frame-of-mind. It's about a process that manages the monster you're creating, and I know few programmers who wouldn't benefit from the type of programming RSpec encourages.

Read this article
Written on Thursday, March 27, 2008