I agree with Sam -- having a higher-level, more abstract testing API has its uses, but it has its costs, too, and you have to weigh the costs vs the benefits.
Here's a technique I've used on several projects: https://github.com/seomoz/interpol/blob/v0.8.1/spec/unit/interpol/configuration_spec.rb#L476-L497 (Notice also the following examples that use that method). In this case, I've defined a class method that defines an example group and defines some corresponding helper objects using `let`. Then I eval the provided block so at the callsite, examples can be defined. I would never start here, but once I've found myself defining the same sort of context 3 or more times, having a helper method which does that for me makes a lot of sense. Note that this doesn't really change the of the specs much; it's still very obviously RSpec. I also left the definition of the context-defining method in-line so that it's easy to see what it does. HTH, Myron On Feb 7, 3:32 pm, Sam Goldman <[email protected]> wrote: > I'm not sure if this is what you are looking for, but I do have some > experience with writing DSLs on top of RSpec. > > I made a project that lets you write tests for HTTP APIs in a bit more of a > straightforward manner[1]. This isn't quite the level of DSL-ness that it > seems you are going for—i.e., the specs using this library still very much > look like RSpec. > > In the context of this project, I think that it was worth it. I very much > enjoy writing specs in the style afforded by this DSL. > > I think the tradeoff is about test noise. Let me define noise as details > that are only relevant at a lower level of abstraction from the spec's > level of abstraction. Ideally, your DSL will only hide noise. If your DSL > hides signal, then consumers of your dialect will need to understand the > internals. > > That said, I think it's a bad idea to start with a DSL. Write your tests > directly, then once you have a good number of tests, ask yourself if a > non-leaky abstraction exists would let you convey your specs at a higher > level. You might find that this leads to a new domain concept for your > production code. Alternatively it might be better to augment your tests > with a little DSL. > > Sorry if that's a little too philosophical :P > > Sam > > 1.https://github.com/smartlogic/http_spec > > On Thu, Feb 7, 2013 at 8:26 AM, Ash Moran <[email protected]>wrote: > > > > > > > > > Hi > > > Someone on another mailing list I'm on recently posted asking for people's > > thoughts on test naming practices, and writing my reply made me think about > > some of the techniques I use to improve naming and remove duplication in my > > own spec files. > > > The most worked-through example I have is the contract test for my > > solutions to the Tennis Kata[1]. (I'm not implying this is the best way to > > tackle the Tennis Kata.) Like with everything other spec suite, I started > > out using plain describe/context/it type language[2], which contains a lot > > of duplication: > > > describe "scoring" do > > before(:each) { tennis.start_game } > > > context "with no advantages" do > > context "A" do > > before(:each) { tennis.point_to_player_a } > > specify { expect(@score).to be == "15-0" } > > > context "A" do > > before(:each) { tennis.point_to_player_a } > > specify { expect(@score).to be == "30-0" } > > end > > > context "B" do > > before(:each) { tennis.point_to_player_b } > > specify { expect(@score).to be == "15-15" } > > > And it so goes on for quite a bit longer in the same style. The first step > > is to factor out the duplication into context helpers, which leaves code > > like this: > > > game_started do > > score_is_now "0-0" > > > context "with no advantages" do > > point_to_player :a do > > score_is_now "15-0" > > > point_to_player :a do > > score_is_now "30-0" > > end > > > The problem now is that the meaning of the specification is now hidden in > > the implementation of the helpers, in this case it was in spec_helper[3]. > > This gives very poorly composed methods with mixed levels of abstraction. > > So the final step is to parameterise the spec DSL with blocks of code from > > the specs itself, allowing you to write: > > > specification_dsl :tennis do > > for_context :game_not_started do > > nothing > > end > > > for_context :game_started do > > tennis.start_game > > end > > > for_context :point_to_player do |player| > > # Heh, just noticed writing this email that I could be doing > > # tennis.send(:"point_to_player_#{player}") here, hey ho > > player == :a ? tennis.point_to_player_a : tennis.point_to_player_b > > end > > > for_context :deuce do > > 3.times do > > tennis.point_to_player_a > > tennis.point_to_player_b > > end > > end > > > to_expect :score_is_now do |expected_score| > > expect(@scores.last).to be == expected_score > > end > > > This has finally put the spec and its definition back together[4], with > > the DSL definition and its voodoo metaprogramming hidden away in > > spec_helper[5]. > > > Unfortunately there's a problem with this implementation, which is that it > > fools RSpec into thinking expectation failures are all coming from > > spec_helper.rb, which makes for rather useless error messages. I haven't > > investigated this. > > > Anyway, the point of explaining this example is to ask for people's > > opinions myself. A few obvious questions are: > > > * What sort of DSL-building have you tried/seen? > > * Is this worth the effort over e.g. helper modules and custom matchers? > > (E.g. is the terseness worth the indirection?) > > * Is this possible in a simpler way with existing context tools in RSpec? > > * If not, is it worth trying to make this DSL definition reusable? > > * Are the situations where this is useful inherently best tackled another > > way? > > > I'm interested in any opinions though, especially if you have a valid > > reason why this is a bad idea / approach. I've sat on it long enough I'm > > clearly not going to have any more insights on my own any time soon. > > > Cheers > > Ash > > > [1]http://codingdojo.org/cgi-bin/wiki.pl?KataTennis > > [2] > >https://github.com/ashmoran/tennis_kata/blob/ff46b7e502337988940ef9ed... > > [3] > >https://github.com/ashmoran/tennis_kata/blob/a159bac3c3d6180c6f1b8377... > > [4] > >https://github.com/ashmoran/tennis_kata/blob/master/spec/tennis_contr... > > [5] > >https://github.com/ashmoran/tennis_kata/blob/master/spec/spec_helper.rb > > > -- > >http://www.patchspace.co.uk/ > >http://www.linkedin.com/in/ashmoran > > > _______________________________________________ > > rspec-users mailing list > > [email protected] > >http://rubyforge.org/mailman/listinfo/rspec-users > > > > _______________________________________________ > rspec-users mailing list > [email protected]http://rubyforge.org/mailman/listinfo/rspec-users -- You received this message because you are subscribed to the Google Groups "rspec" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To post to this group, send email to [email protected]. For more options, visit https://groups.google.com/groups/opt_out.
