Archive for April 2011

rspec and instance variables

April 4, 2011

I was working on watir koans at Watirday 2011 and ran into a snag.  I had assumed I did something careless and needed to look at it when I could focus better.  Later that night I noticed that night I realized I just didn’t understand the scoping rules.  I created a new watir-webdriver object in the before(:all) block and used it in the examples for the example group.  The problem started when I didn’t want to replicate finding the form in later examples so I created a new @f class variable in an example to hold the form but it was nil in the next example.

I was lucky to have an experienced ruby tester, Marek J, there to explain what had happened.  I heard about class variables, classes within classes and copies of variable appearing in the examples.  If you change the variable in one example you will see it go back to the original value in the next example.  This is rspec helping isolate the examples.

Simple right?  No. there is an exception you need to know right away.  If your variable is a reference to something, in the heap, then modifications to that data will be referenced through the class variable.

Ok, so simple variables will be reset but references to complex variables, like a hash, will change keep state between examples?  Well, yes and no, the final example below shows that changing the instance variable to point to a different hash will be reset between examples just like the simple data types.  You kinda have to know about pointer or references to get it.  This happens in Perl too so I understood.  I googled a little and didn’t see it explained so I wrote this blog entry.

The root of what I’m describing is instance variables in classes and subclasses. This is relevant to rspec because:

  • the describe block is magic that makes a class
  • the it block is magic that makes sub class of the describe class
  • the before and after blocks are not in a sub class they are part of the describe class

Because the tester doesn’t see the module or class in their code they may not know what is happening under the covers.  However, they do need to know the behavior or they may learn to just use globals.

require 'rubygems'
require 'rspec'

describe "rspec for simple variables" do
  before(:all) do
    $global   = 'sg'
    @@class   = 'sc'
    @instance = 'si'
  end

 it "should see all simple before variables" do
   $global.should   == 'sg'
   @@class.should   == 'sc'
   @instance.should == 'si'
 end

 it "should change all the simple before variables" do
   $global   = 'new global value'
   @@class   = 'new class value'
   @instance = 'new instance value'
   #modifying a copy of the one defined in the before block

   $global.should   == 'new global value'
   @@class.should   == 'new class value'
   @instance.should == 'new instance value'
 end

  it "should verify changed simple variables in this example" do
    $global.should   == 'new global value'
    @@class.should   == 'new class value'
    @instance.should == 'si'
   # Surprise, @instance.should is still 'si' from the begin block
  end
end

describe "instance references can be changed" do
  before(:all) do
    @instance_hash = {:i_live => "heap memory"}
  end

  it "should see the heap variable" do
    @instance_hash.should == {:i_live => "heap memory"}
  end

  it "should modify the heap variable" do
    @instance_hash[:i_live] = "modified hash"
  end

  it "should see the instance new value via the instance var" do
    @instance_hash[:i_live].should == "modified hash"
    # What just happened? The string was not reset between
    # examples. It points back the same memory but the contents
    # of the memory have changed.
  end

  it "should set the class hash to a different hash" do
    @instance_hash = {:i_live => 'new hash'}
    # This hash will be lost forever when @instance_hash
    # goes out of scope when this example finishes.
  end

  it "should still have the modified hash from the before block" do
    @instance_hash[:i_live].should == "modified hash"
    # The previous example changed the reference of hash so it
    # was restored to point to the one from the begin block.
  end
end

Sorry about the bad formatting, I don’t know what WordPress knobs to turn to make this page wider without paying money.