[rspec-users] Preconditions

Pat Maddox pergesu at gmail.com
Fri Sep 7 20:15:40 EDT 2007


On 9/7/07, Geoffrey Wiseman <geoffrey.wiseman at gmail.com> wrote:
> On 9/7/07, Pat Maddox <pergesu at gmail.com> wrote:
> > describe MyModel, " when saved twice" do
> >
>
> This is the key point I hadn't considered; makes sense, as long as you make
> sure that there's a context wherein the specification that it should have a
> certain number of revisions.  Thanks!
>
> Realized this afternoon that I need to use finer-grained 'test methods' than
> I'm used to.  In some ways the it blocks are closer to assertions than to
> test methods in Test::Unit, in some ways.

Totally.  I highly recommend Dave Astels' "One Expectation per
Example" article [1].

A couple separate points:

* RSpec kicks ass because you can specify behavior on whatever level
you want.  You can use Story Runner to specify a feature, exercising a
vertical slice of your entire app.  You can get really fine-grained
and specify what happens when a method runs.

* Descriptions should be broken up based on the required fixture.  I
don't split them up until I actually have to.  For example, if I'm
writing a Stack class.  I'd probably start off with

describe Stack do
  it "should be empty" do
    Stack.new.should be_empty
  end
end

Then I want to write a spec for when I've added an item.

describe Stack do
  it "should be empty" do
    Stack.new.should be_empty
  end

  it "should not be empty after adding an item" do
    s = Stack.new
    s.add_item :foo
    s.should_not be_empty
  end
end

For a simple spec like this it's okay.  We could factor out the
Stack.new call, and there's one other smell, but we'll get to that in
a minute.

Now what if we want to peek the stack?

describe Stack do
  it "should be empty" do
    Stack.new.should be_empty
  end

  it "should not be empty after adding an item" do
    s = Stack.new
    s.add_item :foo
    s.should_not be_empty
  end

  it "should let you peek at the top after adding an item" do
    s = Stack.new
    s.add_item
    s.peek.should == :foo
  end
end

Now we've got clear duplication in three places:
  (1) The constructor
  (2) Call to add_item
  (3) the 'it' specifier!

It's clear that the fixture for "should not be empty" and "should let
you peek" are the same.  They're also different from the "should be
empty" so we split them up:

describe Stack, " empty" do
  it "should be empty" do
    Stack.new.should be_empty
  end
end

describe Stack, " with one item" do
  before(:each) do
    @stack = Stack.new
    @stack.add_item :foo
  end

  it "should not be empty" do
    @stack.should_not be_empty
  end

  it "should let you peek at the top" do
    @stack.peek.should == :foo
  end
end

There are two key benefits to that.  The first is that it's obvious
where new specifications need to go.  The behavior for #pop whether a
stack is empty or has an item is going to be different.  Also if you
need some behavior that changes with 3 items, you can probably figure
out that you should create a new description.

An even bigger benefit is that it minimizes the brain processing
required to figure out a spec.  If you create the fixture in the setup
and don't vary it, it's trivial to scan through some simple
expectations.

This has to do with the smell that I alluded to earlier, which was the
call to add_item.  Ideally your example will contain just one
expectation and no other setup.  This reduces the concepts that change
from example to example.  Change is where bugs pop up most of the
time.  So if you're doing setup in an example, then you probably want
to split it out from the current description.  In fact, in real life I
would have split the descriptions up immediately after writing the
"should not be empty" example.


> So I went from my first crack,
> closer to:
>
> describe Customer, "xml" do
>   before do
>     # set up customer
>   end
>
>   it "should generate valid summary xml" do
>     # generate summary xml
>     # a bunch of shoulds  about the xml
>   end
>
>   it "should generate valid full xml" do
>     # generate full xml
>     # a bunch of shoulds
>   end
> end
>
> To something like this:
> describe Customer, "full xml" do
>    before do
>      # set up customer full xml
>    end
>
>    it "should have a root node of customer" do
>     @doc.root.name.should == 'customer'
>    end
>
>    it "should contain a customer id"
>   it "should have a name and address"
>
>   #etc
>  end
>
> describe Customer, "summary xml" do
>   # etc.
> end
>
> So I guess I'm still learning the mindset in places.  Although you could
> ahve test methods like that in Test::Unit, most of the time you wouldn't
> bother simply because of the shared setup.  But when the blocks affect how
> the spec is described, it's far more important to have fine-grained
> elements, I think.

There's nothing in Test::Unit that prohibits you from writing tests
with the same granularity that you can achieve with RSpec.  However I
have noticed that it can feel unnatural to do so, whereas RSpec of
course highly encourages it.

> Actually, on that note -- what's the normal way to share a setup across
> multiple behaviors, a helper method in an includeable module?

There are a couple ways to do this.  The first is writing helper
methods, as you said.  For example I use this all the time

describe ProductsController, " requesting /products/1 using GET as a
logged in user" do
  include UserSpecHelpers

  before(:each) do
    login_as mock_user
    @products_proxy = stub("products proxy", :find => :foo)
    mock_user.stub!(:products).and_return @products_proxy
  end

  def do_get
    get :show, :id => "1"
  end

  it "should find the product" do
    @products_proxy.should_receive(:find).with("1").and_return :foo
    do_get
  end

  it "should assign the product to the view" do
    do_get
    assigns[:product].should == :foo
  end
end

module UserSpecHelpers
  def mock_user
    @mock_user ||= mock_model(User, :admin? => false)
  end
end

You can see that we stubbed mock_user#products there.  You can also
set expectations if you want.

Another way is to use a factory.  Check out
http://www.dcmanges.com/blog/38 for a description of that technique.
Though I'd prefer to use mock objects instead of AR instances of
course!

Both of those are pretty fine-grained approaches, allowing you to
share creation of helper objects.  What if there's still duplication,
for example you're typing the same 8 lines of creation code in?  Or if
lots of your examples are the same?  That's where shared behaviors
come in.  Check out the docs [2] for more on how to use them.  They
let you share setup/teardown code AND examples.  In the past if you
wrote three different specs for "/products/1" that had some of the
same examples, you'd probably just remove the common examples from two
of the specs, knowing that the first spec already tested it.  With
shared specs you can actually factor out that behavior but have it be
included into all the specs so they specify the behavior without the
hassle and errors that come with duplication.

Pat


[1] http://daveastels.com/2006/08/26/one-expectation-per-example-a-remake-of-one-assertion-per-test/
[2] http://rspec.rubyforge.org/documentation/index.html


More information about the rspec-users mailing list