On 31 Jul 2010, at 7:06 PM, Myron Marston wrote:
> 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. So he/she
> either defines all helper methods that way, or guesses about which
> ones to define that way (and potentially guesses wrong).
I wonder if this will happen in practice? I can't think of an example off the
top of my head, which isn't to say it won't matter, but it may be better done
pull-based, when the need arises.
> 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.)
This is a fair point. I'm going to the effort of implementing this spike in
rspec-core itself because I *really* want to see if there is value in re-usable
shared examples (my own, admittedly small, side-project already suggests there
is). But I'm fairly sure it's not a pattern in wide use, at least not with
Ruby testing libraries.
> 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.
Funny you mention this. While I've been working on my patch[1] I came to the
same conclusion. This (heavily trimmed down - I may have broken it cutting
bits out for email purposes) example demonstrates it:
module RSpec::Core
describe SharedExampleGroup::Requirements do
it "lets you specify requirements for shared example groups" do
shared_examples_for("thing") do
require_class_method :configuration_class_method, "message"
it "lets you access #{configuration_class_method}s" do
self.class.configuration_class_method.should eq
"configuration_class_method"
end
it "lets you access #{configuration_class_method}s" do
configuration_class_method.should eq "configuration_class_method"
end
end
group = ExampleGroup.describe("group") do
it_should_behave_like "thing" do
def self.configuration_class_method
"configuration_class_method"
end
end
end
group.run_all.should be_true
end
end
end
However, I found a serious issue with class methods, namely that they are being
defined in a persistent class, not a transient ExampleGroup subclass. I
haven't investigated this yet*, but I've left a pending spec at the appropriate
point.
* Random thought after seeing your code: using `class << self; end` over `def
self.x; end` may be a partial answer?
> 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
Thanks for writing this - it's an interesting piece of code. Certainly it also
gets around the class/instance scope divide. But I don't think it can enforce
that the parameter is provided? One of my hopes is to make the errors
completely self documenting.
Aside: one design decision I've made is to make every error due to a missing
requirement fail at the example level, rather than abort the whole spec run.
This is because RSpec-formatted requirements are MUCH easier to read than a
random stacktrace in a terminal. To do this, you need to specify the class
method requirement (comments added for explanatory purposes):
def require_class_method(name, description)
if respond_to?(name)
# We have the class method, so alias it in the instance scope
define_method(name) do |*args|
self.class.send(name, *args)
end
else
# We don't have the class method, so fail all the examples,
# but provide a class-level method so the example definitions
# doesn't fail, and break the run
before(:each) do
raise ArgumentError.new(
%'Shared example group requires class method :#{name}
(#{description})'
)
end
self.class.class_eval do
define_method(name) do |*args|
%'<missing class method "#{name}">'
end
end
end
end
> 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.
I think it's fair to say this is not a simple one to resolve :)
Maybe David has ideas on how to reconcile everything?
Cheers
Ash
[1]
http://github.com/ashleymoran/rspec-core/tree/issue_99_shared_example_block_ordering
--
http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran
_______________________________________________
rspec-users mailing list
[email protected]
http://rubyforge.org/mailman/listinfo/rspec-users