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

_______________________________________________
rspec-users mailing list
rspec-users@rubyforge.org
http://rubyforge.org/mailman/listinfo/rspec-users

Reply via email to