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 rspec-users@rubyforge.org http://rubyforge.org/mailman/listinfo/rspec-users