Love/Hate: RSpec

I decided a couple of months ago that my haphazard unit testing practices were not covering enough of my Rails projects’ code. It’s easy, when you’re the only developer on a project, to get pretty lazy about a lot of things, and testing is one of them. See, I believe firmly in testing. Untested code is unreliable code. I wanted to be more rigorous. So I decided to use RSpec. I’d been poking around it for a while, but I decided to commit and dive in. It has been a rocky road. Read on for my Pro-and-Con review of programming with RSpec.

Wise Investment or Rabbit Hole?

First of all, it took a long time to get up and running. I started out with the documentation at the RSpec site, which is decent but terse. I got a lot more from the Peepcode screencasts, which were very fine. But I soon realized I would have to adjust my thinking to behavior-driven development (BDD, since everything has to have an acronym). Mocks, stubs, distinct suites of tests for controllers and views? After working through all the examples I could find twice, I was still not quite “getting it”–I understood how the examples worked, but when I tried to retrofit my existing applications by writing specs, it was a miserable experience.

It got worse. I was still using Rails 1.2.x, but the spec scaffolding was Rails 2.0 style, with RESTful concepts everywhere and quite a bit of attention to routing. Routing? I was flashing back to the not so good days. Was this really necessary? Did my application really need to pass tests for a non-existent XML layer? Wasn’t this an awful lot of work?

Fortunately, I was in the midst of reading The Rails Way, by Obie Fernandez, which has really good chapters on routing, REST, and RSpec for that matter. So I was able to get some traction, and soon I was working on a new section of one of my applications, a dynamic table for tracking students’ desired grades and actual scores, and I was spec’ing first.

It took a really long time. I thought I’d be done with the whole section in two weeks. Six weeks later I’m about halfway done. I’m going a lot faster now, and I think I’ll get the second half finished in much less time. I’m lucky to have a client who’s both busy with other matters and very patient with me. I guess one was to look at the experience is that I’ve been fortunate to have a chance to learn a technology on the job. Learning RSpec puts a valuable feather in my cap. But has it been worth it? Or would I be better off having delivered my project, sent the invoice, and settled down to work on something else? I guess that depends on how valuable I think RSpec is, specifically to me as a self-learner and one-man programming shop.

Pro and Con

So, without further ado, here are my person thoughts about RSpec after working with it this summer. This is subjective, as I’ll point out below. The executive summary would be “I’m ambivalent.” The details…

Pro: Comprehensive

Using one of the dozen rake tasks available to me after I installed Rspec and Rspec for Rails, I could scaffold up comprehensive specs as well as corresponding models, controllers, and views. These specs encompass all the concerns of Rails 2, so working through them is a sure way to make sure you’re on the right path. The breadth of the specs, especially for controllers, was such that almost every time I would cheat and make a little change to a controller method, I would almost certainly get called on it, especially with autotest running in the background.

Con: Verbose

The flip side of comprehensiveness is verbosity, which feels very un-Rubylike. The starting controller spec clocks in at 430 lines of code. That’s before you start adding any interesting methods. And the view specs can get completely out of hand: it’s very tempting to write what amounts to an element-by-element description of a page’s HTML.

Pro: Lots of Good Tools

Most of the time, it isn’t hard to figure out how to spec something using the tools Rspec gives you. My favorite is probably the ‘xhr’ method for specifying AJAX-initiated requests:

xhr :post, :create, :recordable_subject => {}, :recorder_id => '101'

The rich set of selector options for identifying HTML elements is also awesome.

Con: Inarticulate Error Messages

As good as the tools are, it’s disappointing to get error messages that don’t make sense or don’t tell you anything. For instance, if you accidentally pass an object instead of an integer ID to an Active Record class’s find method, you’re likely to be told that the class doesn’t have a find method. And half of your view spec errors will be explained “<false> is not <true>”. More helpful information would save RSpec’s users a lot of time.

Pro: MVC Enforcement

Rspec makes it very hard to mix up your models’ and your controllers’ concerns. RSpec’s controller specs are designed not to hit the database. You have to stub and spec objects, methods, and return values. This drives you away from examining the internal state of objects, and that’s exactly right. If you feel like you need to test something about a model object’s state, it’s time to write a model spec. Also, if you’re tempted to write a bunch of state-manipulating code into your controller, you’d almost certainly be better off putting it in your models.

Con: Feels Like Work

This is tied to my negative reaction to the verbosity of specs. I’m ripping off Paul Graham’s comment, “Object-oriented programming generates a lot of what looks like work.” For a while, I felt good about creating specs, because it felt like progress. But working and tested code is progress. And the more time you have to spend on specs, the less time you can spend on the code it tests. I have begun to look at spec’ing as less productive and less fun. This morning, for instance, I realized I could make a change to a creation template and change a two-step process into one step. Then I realized how much spec’ing I would have to do—to the controller and the view—and I suddenly wished I had an offshore team to farm it all out to.

Pro: “Should” Vocabulary

The verbosity and work are somewhat mitigated by the ingenious BDD idiom of RSpec. Even using Textmate shortcuts so I don’t have to type out all those repetitive “describe”s and “it should”s, it sinks in anyway. I’m writing specs. I’m describing what the code should do. The idiom warps your thinking in subtle, excellent ways. For instance, I was trying to figure out how routing was working in my application, because I felt the routing specs weren’t giving me the whole picture. I used a trick from Obie’s book and used the Rails console to examine the routing object. Great. But what was I looking for exactly? There sure was a lot of information in there. Then I realized I had a complete list of what the routing object should be doing: I had the specs. And I could paste entire hashes of params and paths into the console to confirm for myself what the routing object was doing. That was a completely unexpected payoff for the work of spec’ing the routes.

Con: Tight Coupling Too Easy

This is the big problem, I think. It’s really a problem with mock objects, but it comes up a lot in RSpec and there isn’t a way completely around it. In a controller spec, you have to tell your spec how your mock objects should behave. You have to tell the spec that in a create method the model object is going to receive a save request. In fact, you have to spell out a lot about how the method is implemented, at the level of the controller and the objects that come into and go out of existence. It isn’t long before you find you’re typing everything twice, copying and pasting code between files, and al sorts of alarms start going off in your head. Is it really necessary to be this precise? If a controller spec passes, and then you make a change to a method that clearly doesn’t break the method—a sanity check in a web browser or mongrel’s tailed dev log proves it—and yet your tests now fail, isn’t something wrong with this picture?

Some Recommendations

So, as I said, I’m not sold on RSpec yet. I like a lot about it, but I think it exacts a price in time, complexity, verbosity, and unwanted coupling. Here are some ways I am trying to minimize these costs.

It’s advisable to stub, not mock, when possible

Stubs are much more forgiving because they don’t establish expectations. So for behavior you don’t think is critical to the task you’re spec’ing, stub, don’t mock. I point you to Martin Fowler’s essay on mocks and stubs. I couldn’t say it better.

DRY it up a bit

Yes, RSpec favors clarity over the Don’t Repeat Yourself directive, but sometimes repetition and verbosity obscure the meaning of specs. As an example, the default spec code for a controller’s routing looks like this:

it "should map { :controller => 'floppy_eared_bunnies', :action => 'show', :master_id => 12, :id => 1 } to masters/12/floppy_eared_bunnies/1" do
route_for(:controller => "floppy_eared_bunnies", :action => "show", :id => 1).should == "/masters/12/floppy_eared_bunnies/1"
end
it "should generate params { :controller => 'floppy_eared_bunnies', action => 'show', :master_id => '12', :id => '1' } from GET /masters/12/floppy_eared_bunnies/1" do
params_from(:get, "/masters/12/floppy_eared_bunnies/1").should == {:controller => "floppy_eared_bunnies", :action => "show", :master_id => '12', :id => "1"}
end

That’s clear insofar as it reads like English, a natural language. But lots of long sentences in English are unclear. And when you’re working with these long statements that wrap in your editor, it gets to be hard work to parse the statements and find the relevant information. A better approach would pull what’s relevant out to the fore and hide all the rest away. It would also, incidentally, DRY things up:

params = {:controller => 'floppy_eared_bunnies', :master_id => "12"}
rtings = [
# SHOW
{:params => params.with(:action => 'show', :id => "1"),
:path => "/masters/12/floppy_eared_bunnies/1",
:method => :get
}, ...
]
rtings.each { |r|
it "should map #{r[:params].to_display_string} to #{r[:path]}" do
route_for(r[:params]).should == r[:path]
end
it "should generate params #{r[:params].to_display_string} from #{r[:method]} #{r[:path]}" do
params_from(r[:method], r[:path]).should == r[:params]
end
}

FYI, that to_display_string method is something I added to the Hash class to provide a nice bracketed listing of keys and values.

Find your own way

Finally, here’s some advice you could get from Lucy Van Pelt for cheap. It all depends on your situation. You have to decide for yourself whether it’s worth it. I usually have to teach myself things. I don’t have a team of people here in my den with me to bounce ideas off of, and I don’t have an employer who will send me to training. So I have to get a lot of use out of the things I take the time to learn, and I have to get that value quickly. So for me RSpec and BBD has been tough. But I see the value, so I’m sticking with it for now.

RSpec is another technology that makes me wonder whether Ruby and Rails will continue to be appropriate one-coder shop technologies, the kind of tools you can use by yourself and get things done quickly, or whether it’s gaining weight and becoming better for teams. I’m going to keep a close eye on that.