[rspec-users] Evaluating shared example customisation block before shared block

David Chelimsky dchelimsky at gmail.com
Sun Aug 1 10:48:01 EDT 2010


On Aug 1, 2010, at 9:43 AM, David Chelimsky wrote:

> 
> On Jul 31, 2010, at 1:06 PM, Myron Marston wrote:
> 
>>> You can still get the same outcome, but you have to implement it in the group like this:
>> 
>>> unless defined?(:foo)
>>> def foo; "foo"; end
>>> end
>> 
>> Good point--I hadn't thought of that.  The one issue I see with it is
>> that the author of the shared example group may not have knowledge of
>> which helper methods consumers will need to override.
> 
> That's no different from methods that have default values for arguments:
> 
>  def foo(bar, baz = :default)
> 
> If you provide only 1 arg, all is well, but the first one is required. Here's the same idea expressed in a group:
> 
> shared_examples_for "foo" do
>  unless defined?(:bar)
>    raise "you need to supply a bar() method"
>  end
> 
>  unless defined?(:baz)
>    def baz; :default; end
>  end
> end
> 
>> So he/she
>> either defines all helper methods that way, or guesses about which
>> ones to define that way (and potentially guesses wrong).
>> 
>>> Maybe a DSL method while I'm working on it?  Maybe:
>>> 
>>>  default_helper(:foo) do
>>>    "foo"
>>>  end
>>> 
>>> WDYT?
>> 
>> If we go the route of having the customization block evaluated first,
>> then I like the idea, but I'm generally wary of adding more DSL
>> methods to RSpec.  I think we should be careful to only add new DSL
>> methods that many people will find useful.  If you find it useful,
>> it's very easy to use it in your project without it being part of
>> RSpec: just define default_helper in a module, and use config.extend
>> YourModule in the RSpec.configuration block.  (Note that I'm _not_
>> against adding this to RSpec: I just want to be sure we don't add a
>> bunch of DSL methods that have limited usefulness.)
>> 
>> Looking back at the initial example that prompted the thread, it looks
>> to me like the primary use case for evaluating the customization block
>> first is so that you can parameterize the shared example group's
>> example descriptions.  (There may be other use cases for defining a
>> class-level helper methods, but none springs to mind).  I also do this
>> frequently.  Often times I have something like this:
>> 
>> [:foo, :bar, :baz].each do |method|
>> it "does something for #{method}" do
>>    subject.send(method).should ...
>> end
>> end
>> 
>> In this case I'm using the method parameter at the class level (to
>> interpolate into the description string) and at the instance level
>> (within the example itself).
>> 
>> If we evaluated the customization block first, it would allow this,
>> but you'd have to define both an instance and class helper:
>> 
>> it_should_behave_like "something" do
>> def self.method_name; :foo; end
>> def method_name; :foo; end
>> end
>> 
>> I think this is a clunky way to essentially pass a parameter to the
>> shared example group.  Better would be something like this:
>> 
>> it_should_behave_like "something" do
>> providing :method_name, :foo
>> end
>> 
>> The instance of the shared example group provides :foo as the value of
>> the method_name parameter.  providing simply defines a class and an
>> instance helper method with the given value.
>> 
>> I've written up an untested gist with a start for the code that would
>> implement this:
>> 
>> http://gist.github.com/502409
>> 
>> I think there's value in evaluating the customization block first and
>> value in evaluating it last.  We can get the best of both worlds if we
>> limit what's evaluated first to a subset (say, a few DSL methods, and
>> maybe all class method definitions), extract it, and evaluate that
>> first, then evaluate the shared block first, then evaluate the
>> customization block.  The gist demonstrates this as well.  This may
>> confuse people, but it does give us the best of both worlds, I think.
> 
> Agreed on both points: best of both worlds and confusing :)
> 
> When I said "maybe a DSL" I was thinking only in the context of the shared group, not in the consuming group.
> 
> What makes the example in your gist confusing to me is that we start to get into a different mental model of what a shared group is. Based on recent changes, for me, it's just a nested example group, which has well understood scoping rules. This introduces a new set of scoping rules that not only make this use case confusing, but it will lead to an expectation that this DSL be made available in other constructs.
> 
> The particular issue of simple values being used in the docstrings and the examples themselves (i.e. exposed to everything in the block scope) could be handled like this:
> 
> shared_examples_for "blah" do |a,b|
>  ...
> end
> 
> it_should_behave_like "blah", 1, 2
> 
> That wouldn't have worked with the old implementation, but it would work perfectly well now. This would also "just work" with hash-as-keyword-args:
> 
> shared_examples_for "blah" do |options|
>  it "blah #{options[:a]}" do
>    ..
>  end
> end
> 
> it_should_behave_like "blah", :a => 1
> 
> Now you can do this:
> 
> [1,2,3].each do |n|
>  it_should_behave_like "blah", :a => n
> end
> 
> And we can still use the customization block to define methods, hooks (before/after) and let(). Now it just feels like the rest of RSpec.

Here's one way the measurement example could work in this format: http://gist.github.com/503432

> 
> Thoughts?
> 
> 
> 
>> 
>> Myron
>> 
>> 
>> On Jul 31, 12:56 am, Ashley Moran <ashley.mo... at patchspace.co.uk>
>> wrote:
>>> On 31 Jul 2010, at 1:10 AM, David Chelimsky wrote:
>>> 
>>>> You can still get the same outcome, but you have to implement it in the group like this:
>>> 
>>>> unless defined?(:foo)
>>>> def foo; "foo"; end
>>>> end
>>> 
>>> Maybe a DSL method while I'm working on it?  Maybe:
>>> 
>>>  default_helper(:foo) do
>>>    "foo"
>>>  end
>>> 
>>> WDYT?
>>> 
>>>> I think it's a good trade-off to put that burden on the group (and it's author) rather that the consumers of the group.
>>> 
>>> Agreed, it'd cause a lot of duplication of effort the other way round.
>>> 
>>> --http://www.patchspace.co.uk/http://www.linkedin.com/in/ashleymoran
>>> 
>>> _______________________________________________
>>> rspec-users mailing list
>>> rspec-us... at rubyforge.orghttp://rubyforge.org/mailman/listinfo/rspec-users
>> _______________________________________________
>> rspec-users mailing list
>> rspec-users at rubyforge.org
>> http://rubyforge.org/mailman/listinfo/rspec-users
> 



More information about the rspec-users mailing list