Thoughts on the Dance-Off

June 7th, 2008

In his Great Test Framework Dance-Off at RailsConf 2008, Josh Susser compared rspec with test/spec and shoulda. All in all I’d say he was very fair in his comparisons and I’d recommend checking out his slides.

There were a couple of dings Josh handed rspec and I’d like to respond to them. This is not intended to sell you on using rspec if you’re not already using it. In fact, you’ll see that I agree with a some of Josh’s criticisms.

I’d much rather see developers using frameworks like test/spec and shoulda than not using anything more expressive than test/unit out of the box, but I also cringe when I hear that someone chose a different framework for reasons that are based on inaccurate or incomplete information. I take full responsibility for that. If you’re basing decisions on inaccurate or incomplete information it’s because I haven’t made the accurate and complete information available to you. This post is one step towards addressing that problem.

Performance

rspec currently runs slower than test/unit and the lightweight frameworks that sit on top of test/unit like test/spec and shoulda. Fortunately, this is something that can be addressed and will become a priority for us in the coming months.

Underlying Complexity

rspec is admittedly a complex and weighty beast. I think this is the result of several problems for which we are starting to look at solutions. We’ve already started to take some steps towards reducing the amount of code that gets loaded (formatters are lazy loaded – only the ones you use get loaded) and we have more work to do on that front.

There are also some efficiencies we can gain by merging the example group and story runners. Right now they both have some good things and some bad, and merging them should give us an opporunity to reduce the bad and take advantage of the good.

For me, the biggest issue is the lack of a rich, formalized extension API. There are some extension points we’ve exposed and documented, but this can definitely be improved.

This is going to be a priority for me in the coming months. I think that once we have that in place, we can start to pull out some of the weight in rspec into plugins that get published separately in much the same vain as Rails and Merb are doing now.

Custom Matchers

In Josh’s talk he pointed out that you can create custom assertions in 4 lines in test/unit frameworks while in rspec it took 30 or so lines. He did point out that the 30 line approach gives you far more control over error messages and that it covers the positive and negative matchers in one shot, but also commented that this extra power comes at a cost.

What Josh didn’t know at the time, and this is definitely worthy of a ding for us not documenting things well enough, is rspec’s simple_matcher method that let’s you create simple matchers in just a few lines. Here’s the example in test/unit from Josh’s talk:

def assert_sorted(actual, message=nil, &block) 
  expected = actual.sort(&block) 
  assert_equal expected, actual, "Order is wrong:" 
end 
assert_sorted(tags) { |a,b| a.name <=> b.name }

And here’s the same thing with simple_matcher:

def be_sorted
  simple_matcher("a sorted list") {|actual| actual.sort == actual}
end
[1,2,3].should be_sorted

The block is handed the actual value. If the block returns true, the expectation passes. If it returns false, it fails with the following message:

expected "a sorted list" but got [1, 3, 2]

If you say [1,2,3].should_not be_sorted you’d get this message instead=:

expected not to get "a sorted list", but got [1, 2, 3]

As of now, you don’t get any control over the failure message other than the string you pass to the simple_matcher method, but I plan to add an optional hash that, if present, will give you more granular control over that message.

FYI – Josh was kind enough to post this on his blog when I sent it to him. Thanks for that Josh.

Macros

Josh really likes the fact that shoulda ships with a bunch of macros like should_only_allow_numeric_values_for and should_render_template. This allows you to structure contexts like this (from the shoulda website):

context "on GET to :show for first record" do
  setup do
    get :show, :id => 1
  end

  should_assign_to :user
  should_respond_with :success
  should_render_template :show
  should_not_set_the_flash

  should "do something else really cool" do
    assert_equal 1, assigns(:user).id
  end
end

This is awesome stuff, and rspec-rails definitely lacks a built in set of macros like this. You can use technoweenie’s rspec_on_rails_on_crack plugin, does, however, to create rspec example groups like this:

describe ThingsController, "GET #index" do
  fixtures :things

  act! { get :index }

  before do
    @things = []
    Thing.stub!(:find).with(:all).and_return(@things)
  end

  it_assigns :things
  it_renders :template, :index
end

You can also create your own custom macro-level matchers quite simply. Take shoulda’s should_respond_with :success from the example above. Here’s how you’d define that in rspec:

module ControllerExampleGroupHelper
  module ClassMethods
    def should_respond_with(expected_response)
      it "should respond with #{expected_response}" do
        response.should be(expected_response)
      end
    end
  end

  class << self
    def included(mod)
      mod.extend ClassMethods
    end
  end
end

Spec::Runner.configure do |config|
  config.include(ControllerExampleGroupHelper, :type => :controller)
end

This adds the should_respond_with method to all of your controller example groups. Obviously, you can just add methods to the ControllerExampleGroupHelper::ClassMethods module and make them available as well.

Here’s how you might use this one:

describe ThingsController do
  context "handling GET /things/1" do
    before(:each) { get :show, :id => "1" }
    should_respond_with :success
  end
end

This outputs the following:

ThingsController handling GET /things/1
- should respond with success

Of course, what we really need is an rspec-shoulda plugin that ships all of these for you. Ideally this could be done by simply making the shoulda macros available in rspec example groups, but my first impression is that shoulda is too tightly tied to test/unit to do that. I’m looking into that possibility though.

In Summary

I’m excited to see that rspec is inspiring other frameworks and that they are challenging rspec to improve on itself. Look for such improvements over the coming months.

I think that whether you choose to use rspec, shoulda, rspec’s mocks or mocha or flexmock, etc, etc, this is a great time for testing frameworks in Ruby and I’m excited to be part of this r-evolution.

4 Responses to “Thoughts on the Dance-Off”

  1. meekish
    meekish Says:

    Good form, David. It’s cool to see everyone sharing ideas.

  2. Craig Buchek
    Craig Buchek Says:

    During the presentation, I let Josh know about simple_matcher, and said that it could reduce the lines required to the equivalent in other frameworks.

    I see RSpec as sort of the “high end” of test frameworks. It’s got more power and expressiveness than the others. I think custom matchers are VERY powerful, and necessary to get the most out of the framework. But I’d definitely like to see a good collection of them to choose from. I’m not sure if that’s best done with a site to choose individual matchers, including some more in the standard package, or having separate add-on packages.

    I’m thrilled to hear you say that there’s more work to be done, to ensure that RSpec keeps ahead of the other frameworks, and speed improvements are in the works.

    Keep up the good work!

  3. Ben Mabey
    Ben Mabey Says:

    Hey David, One of the impediments to rspec adoption I think is that not everyone understands how the DSL works and how to extend it properly. The macros are a good example of this. I have been using macros in my controller specs for a long time. However, I had to look into rspec’s source to understand where to put them. To help alleviate this I have shared what I have learned in a post: http://www.benmabey.com/2008/06/08/writing-macros-in-rspec/

    I have mixed feelings about macros. I like them in some cases but I have seen abuses of them that IMO rely on the developers remembering too much or having a very intimate knowledge of what the macro does.

    I think it is smart that rspec keep macros out of the core since they are very application/domain specific and everyone/team has a different threshold for them. I will try to release some of my controller macros I use for rails and merb. With github I don’t really have an excuse not too. :)

    Also, I think lots of of us on the rspec mailing list would be glad to help out on documentation. What are the weak points in your opinion?

    Thanks for the great framework!

  4. Daniel Tenner
    Daniel Tenner Says:

    Hi David,

    You might also want to have a look at rsl’s skinny_spec plugin:

    http://github.com/rsl/skinny_spec/tree/master

    It’s based on rspec_on_rails_on_crack , but takes things a bit further.

    Thanks for the article.

Leave a Reply (Textile Enabled)