Friends don’t let friends code without tests

Posted on January 8, 2009, under Coding.

But what if your friends don’t write tests? Or what if you’re just lazy? Well, if the latter applies, that’s your own damn fault. But if nobody around you tests their code, you’re probably not going to be motivated to test your own code. But, fear not! RSpec is here!

RSpec is a really awesome testing framework. By “awesome”, I mean that it:

  • makes it easy to write tests;
  • makes your tests easy to read;
  • gives you an easy development path to follow.

It accomplishes this by following behaviour-driven development (BDD). If you’re not a keener, don’t bother following that link. Just read on for the stegosaurus.

With BDD, you write specs that describe your application’s behaviour in terms of what it should and shouldn’t do.

Let’s write an app to mimic one of my favourite dinosaurs. And while we’re at it, we’ll do some testing. But first, install RSpec:

sudo gem install rspec

Stegosauruses are known for 2 things. Let’s start with those awesome tail spikes:

class Stegosaurus  attr_accessor :tail_spikesend

That’s pretty simple. So how do we spec this? We start off by describing what we want our application to do. In this case, we should be able to grab the “tail_spikes” attribute:

require 'stegosaurus'

describe Stegosaurus doit 'should have tail spikes' do  steggie = Stegosaurus.new

  steggie.should respond_to :tail_spikes  steggie.should respond_to :tail_spikes=endend

Run the spec, and we’re off to the races!

$ spec stegosaurus_spec.rb.

Finished in 0.015328 seconds

1 example, 0 failures

We should also spec setting the number of tail spikes, so let’s update the spec:

require 'stegosaurus'

describe Stegosaurus doit 'should have tail spikes' do  steggie = Stegosaurus.new

  steggie.should respond_to :tail_spikes  steggie.should respond_to :tail_spikes=

  steggie.tail_spikes = 4  steggie.tail_spikes.should equal 4endend

That’s one of the most readable and easy-to-understand tests you’ve ever seen, eh?

Stegosauruses are also known for the huge plates on their backs:

class Stegosaurusattr_accessor :tail_spikesattr_accessor :platesend

The spec should be pretty obvious:

require 'stegosaurus'

describe Stegosaurus doit 'should have tail spikes' do  steggie = Stegosaurus.new

  steggie.should respond_to :tail_spikes  steggie.should respond_to :tail_spikes=

  steggie.tail_spikes = 4  steggie.tail_spikes.should equal 4end

it 'should have plates' do  steggie = Stegosaurus.new

  steggie.should respond_to :plates  steggie.should respond_to :plates=

  steggie.plates = 12  steggie.plates.should equal 12endend

Notice, though, that we’re beginning to repeat ourselves. In both of those examples (#it blocks), we create a new Stegosaurus. Let’s tidy that up a bit:

require 'stegosaurus'

describe Stegosaurus dobefore :each do  @steggie = Stegosaurus.newend

it 'should have tail spikes' do  @steggie.should respond_to :tail_spikes  @steggie.should respond_to :tail_spikes=

  @steggie.tail_spikes = 4  @steggie.tail_spikes.should equal 4end

it 'should have plates' do  @steggie.should respond_to :plates  @steggie.should respond_to :plates=

  @steggie.plates = 12  @steggie.plates.should equal 12endend

What we did there is tell RSpec to create a new Stegosaurus before each example is run. To make the stegosaurus available to the examples, it simply needs to be an instance variable.

When running your specs, there are a few different ways to format the output. I tend to use the default and “specdoc” formats. Currently, our spec’s output looks like this:

$ spec stegosaurus_spec.rb..

Finished in 0.01591 seconds

2 examples, 0 failures

But we can also tell RSpec to spit out a summary of our specs:

$ spec stegosaurus_spec.rb --format specdoc

Stegosaurus- should have tail spikes- should have plates

Finished in 0.057443 seconds

2 examples, 0 failures

Our Stegosaurus class is feeling a bit weird, though..technically, we can set the “tail_spikes” and “plates” attributes to any object:

$ irb>> require 'stegosaurus'=> true>>?> steggie = Stegosaurus.new=> #<Stegosaurus:0x602afc>>>?> steggie.tail_spikes = "we don't want to be able to do this!"=> "we don't want to be able to do this!"

So we need to restrict those two attributes to integers:

class Stegosaurusattr_reader :tail_spikesattr_reader :plates

def tail_spikes=(number_of_tail_spikes)  raise ArgumentError, 'The first argument (number_of_tail_spikes) must be a Fixnum.' unless number_of_tail_spikes.is_a? Fixnum

  @tail_spikes = number_of_tail_spikesend

def plates=(number_of_plates)  raise ArgumentError, 'The first argument (number_of_plates) must be a Fixnum.' unless number_of_plates.is_a? Fixnum

  @plates = number_of_platesendend

With that done, what do we need to change in our specs? We need to spec the behaviour of the 2 new setter methods:

require 'stegosaurus'

describe Stegosaurus dobefore :each do  @steggie = Stegosaurus.newend

describe '"tail_spikes" attribute' do  it 'can only be set to an integer' do    Proc.new {      @steggie.tail_spikes = 'asdf'      }.should raise_error ArgumentError, 'The first argument (number_of_tail_spikes) must be a Fixnum.'

    @steggie.tail_spikes = 4    @steggie.tail_spikes.should equal 4  endend

describe '"plates" attribute' do  it 'can only be set to an integer' do    Proc.new {      @steggie.plates = 'asdf'      }.should raise_error ArgumentError, 'The first argument (number_of_plates) must be a Fixnum.'

    @steggie.plates = 12    @steggie.plates.should equal 12  endendend

Most of that is pretty self-explanatory. However, notice that we’ve just nested #describe inside of the original #describe…twice! We do this because it makes are specs read more fluidly, and it groups examples (#it blocks) together. And inside these inner-#describes, we can do anything we talked about earlier, like setup a before-block.

Let’s add one more attribute…a name:

class Stegosaurusattr_reader :tail_spikes, :plates, :name

def tail_spikes=(number_of_tail_spikes)  raise ArgumentError, 'The first argument (number_of_tail_spikes) must be a Fixnum.' unless number_of_tail_spikes.is_a? Fixnum

  @tail_spikes = number_of_tail_spikesend

def plates=(number_of_plates)  raise ArgumentError, 'The first argument (number_of_plates) must be a Fixnum.' unless number_of_plates.is_a? Fixnum

  @plates = number_of_platesend

def name=(name)  raise ArgumentError, 'The first argument (name) must be a String.' unless name.is_a? String

  @name = nameendend

And now the specs look like:

require 'stegosaurus'

describe Stegosaurus dobefore :each do  @steggie = Stegosaurus.newend

describe '"tail_spikes" attribute' do  it 'can only be set to an integer' do    Proc.new {      @steggie.tail_spikes = 'asdf'    }.should raise_error ArgumentError, 'The first argument (number_of_tail_spikes) must be a Fixnum.'

    @steggie.tail_spikes = 4    @steggie.tail_spikes.should equal 4  endend

describe '"plates" attribute' do  it 'can only be set to an integer' do    Proc.new {      @steggie.plates = 'asdf'    }.should raise_error ArgumentError, 'The first argument (number_of_plates) must be a Fixnum.'

    @steggie.plates = 12    @steggie.plates.should equal 12  endend

describe '"name" attribute' do  it 'can only be set to a string' do    Proc.new {      @steggie.name = ['not valid']    }.should raise_error ArgumentError, 'The first argument (name) must be a String.'

    @steggie.name = 'Rupert'    @steggie.name.should equal 'Rupert'  endendend

Let’s run that spec now:

$ spec stegosaurus_spec.rb..F

1)'Stegosaurus "name" attribute can only be set to a string' FAILEDexpected "Rupert", got "Rupert" (using .equal?)./stegosaurus_spec.rb:37:

Finished in 0.02225 seconds

3 examples, 1 failure

Hrmmm, that’s no good. But that’s a fairly useful error message, eh?…well, sort of. It’s telling us what it expected, and what it received. The thing is, it expected “Rupert” and got “Rupert”, so what’s going on? The problem lies in the fact that you can’t use #equal? to compare strings, and that’s exactly what we’re doing. If you don’t believe me, try it for yourself:

$ irb>> 'Rupert'.equal? 'Rupert'=> false

BTW, did you notice the “(using .equal?)” hint that RSpec gave us?

Enough banter. All we need to do is change this:

@steggie.name.should equal 'Rupert'

to this:

@steggie.name.should == 'Rupert'

Shall we view our specs in all their glory now?

$  spec stegosaurus_spec.rb --format specdoc

Stegosaurus "tail_spikes" attribute- can only be set to an integer

Stegosaurus "plates" attribute- can only be set to an integer

Stegosaurus "name" attribute- can only be set to a string

Finished in 0.018755 seconds

3 examples, 0 failures

Those’re the absolute basics of RSpec. If you’re thirsty for more, check out the RSpec website.

Share and Enjoy:
  • Twitter
  • Digg
  • Reddit
  • StumbleUpon
  • del.icio.us
  • Facebook
  • Print

4 Replies to "Friends don’t let friends code without tests"

gravatar

R.T.  on January 17, 2009

Also, "you can't use #equal? to compare strings" is FALSE! I can prove it:

>> String.respond_to?(:equal?)
=> true

You *can* use equal? to compare Strings, it's just looking for equality by object rather than value.

>> foo = "internets"
=> "internets"
>> bar = foo
=> "internets"
>> foo.class
=> String
>> bar.class
=> String
>> foo.equal? bar
=> true

gravatar

R.T.  on January 17, 2009

Er, forget “Also”, comment failed, so here goes again:

If you want to be truly behaviour *driven*, you should create the spec *before* the code. E.g.

# stegosaurus_spec.rb
describe Stegosaurus do
end

Running this will produce the error “uninitialized constant Stegosaurus”, which is what “drives” you to create the Stegosaurus class.

gravatar

Nick  on January 17, 2009

> Also, "you can't use #equal? to compare strings" is FALSE!

You're right. Technically, it's false. However, you can't use #equal to test String comparison in the way most people expect to be able to.

Your example does in fact work, but it's an edge case.

The most common usage is along the lines of:

>> x = 'foo'
>> y = 'foo'
>> x.equal? y
=> false

gravatar

Nick  on January 17, 2009

> you should create the spec *before* the code

Yup, you should!

I specifically didn't mention that, because many people, especially newbs, don't bother adhering to it. My focus with these posts on testing is to get people writing tests. The other facets, such as specs before code, can come later.

I sometimes write specs before code, and sometimes write code before specs. Ultimately, it comes down to how I feel about the code I intend on writing next. If it's a basic, mindless chunk, then the code comes first, and sometimes in multiple chunks. Otherwise, I'll begin by speccing it out.

Leave a Comment