Entries Tagged 'RSpec' ↓

RSpec, and when it loads (objects, modules, etc)

Lately I’ve been playing around with RSpec, which is a fantastic testing tool that falls under the umbrella of what’s being called “Behavior Driven Development” (BDD).  What BDD advocates is that there should be a common language used to talk about your system, and that describing the behaviors of the system ensures that you are delivering what’s really valuable, instead of writing lots of code that may not be necessary.  If you are interested in learning more about BDD, I suggest you head over here and start reading, as it seems to be the resource to go to for more information. Anyway, I’m not here to talk about some quirks with RSpec in the hope that someone will stumble over here and provide some insight. Let’s start with some code and work from there towards my conclusions:

Here is a simple class representing a digital photo that you might store on a server, your computer, wherever. This is as simplified as I want it to be for the purposes of illustrating my points.

#!/usr/bin/ruby

class Photo
  attr_reader :height, :width

  def initialize( width, height )
    @width = width
    @height = height
  end

  def scale_absolute( width, height )
    @width = width
    @height = height
  end

  def scale_percentage( width, height )
    @width *= ( width / 100.0 )
    @height *= ( height / 100.0 )
  end
end

Our second code file is just a module of helper methods that I’ve creatively titled HelperMethods

module HelperMethods
  def nice_photo_resolution( photo )
  if photo.respond_to?( :width ) && photo.respond_to?( :height )
  return "#{photo.width}x#{photo.height}"
  end
  end
end

And here is our rspec code for testing the Photo class. We just simply use the describe method to create an ExampleGroup instance, setup some basic values, have a before method to instantiate an instance of a Photo, and then our it methods which create Example instances to be run.

#!/usr/bin/ruby

require 'photo'
require 'helper_methods'

describe Photo do
  include HelperMethods

START_HEIGHT = rand(640)
  START_WIDTH = rand(480)

before( :each ) do
  @photo = Photo.new( START_WIDTH, START_HEIGHT )
  end

it "should resize a photo from #{START_WIDTH}x#{START_HEIGHT}to 640x480" do
  @photo.scale_absolute( 640, 480 )
  @photo.width.should == 640
  @photo.height.should == 480
  end

it "should scale a photo 200% from #{START_WIDTH}x#{START_HEIGHT} to #{START_WIDTH * 2}x#{START_HEIGHT * 2}" do
  @photo.scale_percentage( 200, 200 )
  @photo.width.should == ( START_WIDTH * 2 )
  @photo.height.should == ( START_HEIGHT * 2 )
  end

it "should scale a photo 50% from #{START_WIDTH}x#{START_HEIGHT} to #{( START_WIDTH * 0.50 )}x#{( START_HEIGHT * 0.50)}" do
  @photo.scale_percentage( 50, 50 )
  @photo.width.should == ( START_WIDTH * 0.50 )
  @photo.height.should == ( START_HEIGHT * 0.50 )
  end

it "should rotate a photo 90 degrees such that the dimensions change from #{START_WIDTH}x#{START_HEIGHT} to #{START_HEIGHT}x#{START_WIDTH}" do
  @photo.rotate( 90 )
  @photo.width.should == START_HEIGHT
  @photo.height.should == START_WIDTH
  end
end

Now, as I’ve been playing around, I’ve noticed several things that have stuck in my craw. The first is that even though I’m including HelperMethods in the describe block, if I try to use it in the description argument passed to the it method, I get an error trying to run my spec test telling me that no such method exists. Take a look here to see what I mean:

describe Photo do
  include HelperMethods

START_HEIGHT = rand(640)
  START_WIDTH = rand(480)

before( :each ) do
  @photo = Photo.new( START_WIDTH, START_HEIGHT )
  end

it "should resize a photo from #{nice_photo_resolution( @photo )}to 640x480" do
  @photo.scale_absolute( 640, 480 )
  @photo.width.should == 640
  @photo.height.should == 480
  end

...
end

When I try to run the spec command on that ExampleGroup, I get an error saying the nice_photo_resolution method cannot be found. This leads me to believe RSpec is doing something with the it method before it builds the ExampleGroup instance from the describe method. Further supporting this theory is that when you try to instantiate instance level variables in a before method, they cannot be seen and are evaluated as nil in the code as its executed. Take a look here for an example:

describe Photo do
  include HelperMethods

before( :each ) do
  @start_width = rand( 640 )
  @start_height = rand( 480 )

@photo = Photo.new( @start_width, @start_height )
  end

it "should resize a photo from #{@start_width}x#{@start_height}to 640x480" do
  @photo.scale_absolute( 640, 480 )
  @photo.width.should == 640
  @photo.height.should == 480
  end
...
end

That example is a little contrived, but I think it gets the point across. Moving to a more realistic example, this produces the same error:

describe Photo do
  include HelperMethods

before( :each ) do
  @photo = Photo.new( rand( 640 ), rand( 480 ) )
  end

it "should resize a photo from #{@photo.width}x#{@photo.height}to 640x480" do
  @photo.scale_absolute( 640, 480 )
  @photo.width.should == 640
  @photo.height.should == 480
  end
...
end

This will cause the spec command to throw an error, because @photo is evaluating to nil, and trying to access the width or height instance fields of the object cause an error to be thrown.

I’m not entirely sure that my imperfect understanding of Ruby class instantiation is at fault for my confusion, but it doesn’t seem like it should be the case. I also haven’t cracked open the RSpec code yet, as I am just trying to learn the system now and don’t want to have to dive into the gory details until I understand how to use it to get work done. If anyone can point me in the right direction though I would truly appreciate it. These examples are fairly unrealistic, but I think it’s a short jump to seeing how not understanding the order of construction could lead to some problems when working on production-level code. I’d rather be testing my code than testing the RSpec system.