[rspec-devel] [ rspec-Feature Requests-9472 ] Provide way to extract specify/it into includable module

noreply at rubyforge.org noreply at rubyforge.org
Wed May 2 23:15:46 EDT 2007


Feature Requests item #9472, was opened at 2007-03-22 16:12
You can respond by visiting: 
http://rubyforge.org/tracker/?func=detail&atid=3152&aid=9472&group_id=797

Category: expectation module
Group: None
>Status: Closed
Priority: 3
Submitted By: Bob Cotton (bcotton)
>Assigned to: David Chelimsky (dchelimsky)
Summary: Provide way to extract specify/it into includable module

Initial Comment:
Based on the conversation here http://rubyforge.org/pipermail/rspec-users/2007-March/000993.html

provide the ability to extract specify/it into a module that may be included into a context/describe

For example:


describe AllEditions, :shared => true do
 it "should" do
 end
end

describe LargeEdition do
  include AllEditions

  it "should also" do
  end
end



----------------------------------------------------------------------

>Comment By: David Chelimsky (dchelimsky)
Date: 2007-05-03 03:15

Message:
This is fixed by [#9605] Patch for ER 9472, shared behaviour, which was committed to trunk in rev 1820 and released with RSpec-0.9.1.

----------------------------------------------------------------------

Comment By: Bob Cotton (bcotton)
Date: 2007-04-06 18:31

Message:
 > Date: 2007-04-03 06:36
 > Sender: David Chelimsky
 > So the concern that we've had about this sort of sharing
is that
 > it wouldn't lead to cleaner specs, but would, in fact, end up
 > making things more complex. Let's take the stack example.
 > 
 > describe "All Stacks", :shared => true do
 >   it "should return the top item when you send it #top"
 > do
 > 
 > OK - right away we've got a problem. Somehow, this
example needs
 > to reference a Stack instance and say something about it:
 > 
 > describe "All Stacks", :shared => true do
 >   it "should return the top item when you send it #top"
 > do
 >     @stack.top.should == ???????

Sharing of instance variables, especially instance variables
that are
the target of the spec was exactly what I need from spec
sharing.

 > What's it going to equal? Where's it going to come from?
Perhaps
 > this:
 > 
 > describe "All Stacks", :shared => true do
 >   it "should return the top item when you send it #top"
 > do
 >     @stack.top.should == @last_added
 >   end
 > end
 > 
 > describe "An non-empty, non-full Stack" do
 >   it_should_behave_as "All Stacks"
 >   
 >   def setup
 >     @stack = Stack.new
 >     @stack.push "1"
 >     @stack.push @last_added = "2"
 >   end
 > end
 >
 > Now this would pass, but there's this unfortunate binding
between
 > the shared behavoiur and its user. As things grow more
complex,
 > these will end up in different files (or one really big file)
 > and you'll have to go chasing around to understand what
any one
 > thing is doing. 

I agree that the possibility for this complexity exists, but
it's
really no worse than included modules with helper methods
that are
referencing instance vars created in setup or ruby modules
that depend
on instance vars or methods present on the including class.

 > So then my question is what other use cases are there for
:shared
 > => true?

Most of our use for rspec is for acceptance/function
testing. We have
several editions of our product. We need a way to
parameterize the
creation of each edition, then run some specs over each of them.

My first thought was to extend 'describe' to accept a list
of modules,
then create a new Behavior for each one:

describe "All Editions", LargeEditionFactory,
SmallEditionFactory do
end

This would create a Beahviour for each module, include the
module on
the Behavior, then add it to the list.

All of the included modules would respond to the same methods,
specifically a method to create the correct edition.

Then you suggested shared behaviours. Which does the same thing,
except the non-shared behavior would be responsible for the
creation
of the edition. But the shared behaviors would still have to
have
knowledge of the instance of the edition from the non-shared
behaviour. 

That's our use case.


----------------------------------------------------------------------

Comment By: David Chelimsky (dchelimsky)
Date: 2007-04-03 12:36

Message:
So the concern that we've had about this sort of sharing is that it wouldn't lead to cleaner specs, but would, in fact, end up making things more complex. Let's take the stack example.

describe "All Stacks", :shared => true do
  it "should return the top item when you send it #top" do

OK - right away we've got a problem. Somehow, this example needs to reference a Stack instance and say something about it:

describe "All Stacks", :shared => true do
  it "should return the top item when you send it #top" do
    @stack.top.should == ???????
    
What's it going to equal? Where's it going to come from? Perhaps this:

describe "All Stacks", :shared => true do
  it "should return the top item when you send it #top" do
    @stack.top.should == @last_added
  end
end

describe "An non-empty, non-full Stack" do
  it_should_behave_as "All Stacks"
  
  def setup
    @stack = Stack.new
    @stack.push "1"
    @stack.push @last_added = "2"
  end
end

Now this would pass, but there's this unfortunate binding between the shared behavoiur and its user. As things grow more complex, these will end up in different files (or one really big file) and you'll have to go chasing around to understand what any one thing is doing.

Now there is a case in which I see a real use for this that would minimize this binding and provide some benefit - and that is defining a duck type - sort of like a java interface without the interface class. So you could do this:

describe "Enumerable", :shared => true do
  it "should respond to #each" do
    enumerable.should respond_to(:each)
  end
end

describe "A Stack" do
  it_should_behave_as "Enumerable"
  
  def setup
    @stack = Stack.new
  end

  def enumerable
    @stack
  end
  
end

So now we've defined an interface from the examples instead of from a class from which Stack must derive. This is what duck typing is all about! But we still have an odd binding here - the "A Stack" behaviour has to implement #enumerable. That makes me uncomfortable.

So I was batting this around w/ Dan North and he suggested something that would end up looking like this:

act_as_enumerable = act_as %w { each }

describe Stack do
  it "should act as enumerable" do
    Stack.new.should act_as_enumerable
  end
end

Actually - his suggestion was quack_like :)

quack_like_enumerable = quack_like %w { each }

describe Stack do
  it "should quack like enumerable" do
    Stack.new.should quack_like_enumerable
  end
end

One way or another, I think that we'll want to add something like that because it is a very simple way to solve the behaves_as problem.

So then my question is what other use cases are there for :shared => true?


----------------------------------------------------------------------

Comment By: David Chelimsky (dchelimsky)
Date: 2007-03-27 01:24

Message:
I need to think about how to word some things. Why don't you go ahead and give it your best shot and I'll tweak what I think needs tweaking. OK?

Thanks Bob - a lot of people have been waiting for a feature like this.

Cheers,
David

----------------------------------------------------------------------

Comment By: Bob Cotton (bcotton)
Date: 2007-03-27 01:01

Message:
Thoughs on the wording for including shared behavior, before
I write the doc and submit the patch?

----------------------------------------------------------------------

Comment By: Bob Cotton (bcotton)
Date: 2007-03-25 13:36

Message:
Yes, I'll be doing the work on 0.9. 

No BDD pie necessary, not being that familar with the code
base, just wanted to make sure I wasn't missing anything.

Before I can define behavior, I want to make sure I've got
all the right classes.

Don't worry, there will be specs!

Thanks for the followup

----------------------------------------------------------------------

Comment By: David Chelimsky (dchelimsky)
Date: 2007-03-24 23:33

Message:
Following up - I guess there's nothing wrong w/ talking about design a LITTLE first.

How about a collection on Spec::Runner with methods to interact with it right on the module. Something like:

Spec::Runner.add_shared_behaviour(behaviour)
Spec::Runner.find_shared_behaviour(:name)

This way you don't have to expose anything and those methods can handle the management of the collection.

Thoughts? Feel free to throw a BDD pie in my face instead of answering.

Cheers,
David

----------------------------------------------------------------------

Comment By: David Chelimsky (dchelimsky)
Date: 2007-03-24 23:24

Message:
Hi Bob - I assume you're working in the 0.9 branch, yes? If not, please do.

As for your questions - this is BDD! Write some specs! If we have any issues w/ the design, we'll talk about it later. Cool?

----------------------------------------------------------------------

Comment By: Bob Cotton (bcotton)
Date: 2007-03-24 22:29

Message:

I'm about to start implementing this. A couple of questions:

1. I suppose the only way to locate a shared 'describe'
would be through its description
2. I'll need to add an attr_reader for :examples to
Spec::DSL::Behavior::ModuleMethods so I can grab all shared
Examples and add them to the current Behavior just before
running.
3. I'll need to add either an attr_reader for :behaviors or
a find method to Spec::Runner::BehaviorRunner so I can
lookup behaviors by descritpion.

Thoughts?

----------------------------------------------------------------------

Comment By: Bob Cotton (bcotton)
Date: 2007-03-22 18:45

Message:

Very nice.

it_should_behave_as

Brainstorming with a co-worker:
behaving_as
behaves_like
sharing
acts_like
an_example_of
shares_behavior_with
shares_behavior_of
same_as
same_behavior_as
just_like
equivalent_to
it_doppelgangers

----------------------------------------------------------------------

Comment By: David Chelimsky (dchelimsky)
Date: 2007-03-22 16:23

Message:
I like this idea in general. I know we've been resistant to add any sort of example (spec) sharing, but perhaps now is the time.

We can't really just say describe AllEditions because Ruby would treat that as a constant and not be able to find it. Also, include implies a module - this is a bit different. So I'm thinking something like this:

describe "AllEditions", :shared => true do
 it "should do something" do
 end
end

describe LargeEdition do
  it_should_behave_as "AllEditions"

  it "should also do something else" do
  end
end

That reads kinda nice, ay?


----------------------------------------------------------------------

You can respond by visiting: 
http://rubyforge.org/tracker/?func=detail&atid=3152&aid=9472&group_id=797


More information about the rspec-devel mailing list