Thursday, December 27, 2007

Hmm... That title looks awfully unimpressive: it. However, that simplicity is one of the beauties of Ruby: how the language let you write such cool stuff like:

it "should do blah blah blah" do
    do_it
    check_it
end

So, without further ado, let's jump in.

Looking at the rspec rdocs (making sure that I'm looking at the 1.1.1 docs), it creates a Spec::Example::Example and adds it to the current example group (the one creates by the describe method). Here's the code:

     # File lib/spec/example/example_group_methods.rb, line 97
 97:       def it(description=nil, &implementation)
 98:         e = new(description, &implementation)
 99:         example_objects << e
100:         e
101:       end

That new call is interesting to me. We are currently in example_group_methods inside of an ExampleGroup. If we look at initialize on ExampleGroup, it just sets the @_defined_description and @_implementation.

In SharedExampleGroup, it also calls describe and the like.

I just noticed this:

class SharedExampleGroup < Module

class ExampleGroup

Why is SharedExampleGroup < Module? I guess I assumed that SharedExampleGroup < ExampleGroup, but that isn't the case. I can see that Ruby doesn't enforce any sort of type hierarchy, since the types are so amorphous, but it makes sense from a design perspective. Unless, of course, I'm completely misunderstanding the relationship.

[NOTE: I posted a message to the rspec-users on this (I probably should have sent it to rspec-dev). I will post any information I found out as an update to this entry.]

In any case, the it method creates a new example group, adds it to the example_objects array (why isn't it @example_objects?). Oh, I see:

def example_objects
        @example_objects ||= []
end

Interesting. Lazy initialization of an instance variable. I will have to keep this pattern in mind, especially as I continue to look through the code here. For those who don't know, the ||= operator is shorthand for a = a || b. So, if a is nil, then the value of b is used, otherwise a is returned. Couple that with the fact that the return value of an assignment expression is the value assigned, we get a great pattern:

def return_and_assign
     a ||= b
end

If a is not nil, then it is assigned to itself and returned. If a is nil, then it gets the value of b and is returned. Cool. Put this into a method to retrieve an instance variable and you get lazy initialization.

In any case, this ends the overview of it. Basically, it creates a new Example, adds it to the example_objects instance variable and then returns the just created Example.

This ends the initial setup of your specs, getting ready for a runner to run them. Because I am impatient, we are going to skip the code for the runner and start investigating what happens in the code block that you pass to it: that is, your actual spec. This will lead us into matchers, which is where the action is.

Thursday, December 27, 2007 11:50:36 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0]