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
[email protected]
http://rubyforge.org/mailman/listinfo/rspec-users