[rspec-users] Building higher-level DSLs

Ash Moran ash.moran at patchspace.co.uk
Thu Feb 7 18:26:33 UTC 2013


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/ff46b7e502337988940ef9ed881c934a04c53766/spec/tennis_spec.rb
[3] https://github.com/ashmoran/tennis_kata/blob/a159bac3c3d6180c6f1b83770e6cd678fa750b33/spec/spec_helper.rb
[4] https://github.com/ashmoran/tennis_kata/blob/master/spec/tennis_contract.rb
[5] https://github.com/ashmoran/tennis_kata/blob/master/spec/spec_helper.rb

-- 
http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashmoran



More information about the rspec-users mailing list