On 31 Jul 2010, at 7:06 PM, Myron M. 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.
end
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?
I’ve written up an untested gist with a start for the code that would
implement this:
example_group.rb · GitHub
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