Wednesday, May 7, 2014

Why I Dig RSpec

I've been using RSpec a fair amount lately, so I thought I'd talk about some of its features I really dig.

The rspec -f d command

If contexts and describes are favored over loaded setup and it blocks that do too much, you can get some really nice, verbose output from this command.

For example, suppose I have some specs describing a #wear_hat? method of a SimpleDecisions class. My rspec -f d output might look something like the following:

SimpleDecisions
  #wear_hat?
    when I am a vampire
      and it's sunny out
        returns true
      and it's night time
        returns false
    when I am at a home baseball game
      returns true
    when I am at an away baseball game
      and my hat is for my home team
        returns false

Pretty nice, huh? The specs almost match what we would expect the structure of the implementation to be!

Template specs

There's a lot of debate out there about whether test code should be DRY. Some say it shouldn't and that tests should flow and provide context. Others assert (no pun intended) that tests should be maintainable and, therefore, just as clean as (or cleaner than) production code. Template specs meet somewhere in the middle. They can replace many repeated tests at the (slight) expense of maintainability by looping through values and outcomes. For example, a template spec on an absolute value method might look like:

describe Math do

  describe '.abs' do
    [
      [42, 42],
      [0, 0],
      [-42, 42]
    ].each do |given_number, expected_number|

      context "given #{given_number}" do
       subject { described_class.abs(given_number) } 

       it { should be(expected_number) }
      end

    end
  end

end

In my mind, templates are nice when testing a pure function or when there are multiple, similar tests that can be aggregated.

Argument matchers

These are very cool. For example, say I want to test that an error is logged in a certain case, and I don't care about the specific error text, rather I just want the message to be some String. I could write something like this:

  # ...
  it 'logs an error' do
    ErrorLogger.should_receive(:write).with(kind_of(String))
    # cause error
  end
  # ...

This is a fairly trivial example, but these things are powerful. For a further example, presume ErrorLogger.write actually takes two parameters but I don't care about the second. The line would then become ErrorLogger.should_receive(:write).with(kind_of(String), anything).

Natural decoupling

By providing methods like subject and described_class and providing mechanisms to easily redefine the former, RSpec decouples the specs from things like the name of the class under test or the method being tested. For example, in my above absolute value example, if I changed the class from Math to MathUtils then, assuming everywhere below I referenced Math as described_class, I would only have to rename at the top of the file. One change in the production code would merit one change in the tests. Perfect!

Conclusion

Well, I hope I've expressed some of my favorite things about RSpec. If you would like to see any of my specs, they shouldn't be hard to find in my ruby repositories on github. If you have any comments on these points, or anything you really like about RSpec (or dislike for that matter) I'd love to hear your comments.

No comments:

Post a Comment