Bowling with rSpec
Now THIS is fun. Here's the bowling game test driven in Ruby with the new rspec (v0.5).
NOTE - updated on 4/15/2006 - see "Spec using v0.4" for the original post
require 'spec'
context "A bowling score calculator" do
setup do
@game = Game.new
end
specify "should score 0 for an all gutter game" do
(1..20).each { @game.roll(0) }
@game.score.should.be 0
end
specify "should score 20 for an all ones game" do
(1..20).each { @game.roll(1) }
@game.score.should.be 20
end
specify "should score 150 for an all fives game" do
(1..21).each { @game.roll(5) }
@game.score.should.be 150
end
specify "should score 300 for a perfect game" do
(1..12).each { @game.roll(10) }
@game.score.should.be 300
end
end
This was the original post
require 'rubygems'
require_gem 'rspec'
class GameSpec < Spec::Context
def setup
@game = Game.new
end
def gutter_game_score_should_be_zero
(1..20).each { @game.roll(0) }
@game.score.should.be 0
end
def all_ones_game_score_should_be_20
(1..20).each { @game.roll(1) }
@game.score.should.be 20
end
def all_fives_game_score_should_be_150
(1..21).each { @game.roll(5) }
@game.score.should.be 150
end
def perfect_game_score_should_be_300
(1..12).each { @game.roll(10) }
@game.score.should.be 300
end
end
Now, I'll confess that some of the methods in Game are a bit ugly (doing sneaky things and double duty) in this implementation (and there's a smelly temporary field), but I was trying to see how far I could push the ease of language in the score method.
class Game
def initialize
@rolls = []
@roll = 0
end
def roll(pins)
@rolls.push pins
end
def score
score = 0
@roll = 0
(1..10).each do
if strike?
score += 10 + next_2_rolls_for_strike
elsif spare?
score += 10 + next_roll_for_spare
else
score += sum_of_rolls_in_frame
end
end
score
end
def strike?
@rolls[@roll] == 10
end
def spare?
@rolls[@roll] + @rolls[@roll + 1] == 10
end
def next_2_rolls_for_strike
@roll += 1
@rolls[@roll] + @rolls[@roll + 1]
end
def next_roll_for_spare
@roll += 2
@rolls[@roll]
end
def sum_of_rolls_in_frame
@roll += 2
@rolls[@roll - 2] + @rolls[@roll - 1]
end
end
When David showed me this, I immediately thought "A recursive solution would probably be cleaner and more understandable". So here it is.
require 'spec'
class GameSpec < Spec::Context
def setup
@game = Game.new
end
def gutter_game_score_should_be_zero
(1..20).each { @game.roll(0) }
@game.score.should.be 0
end
def all_ones_game_score_should_be_20
(1..20).each { @game.roll(1) }
@game.score.should.be 20
end
def all_fives_game_score_should_be_150
(1..21).each { @game.roll(5) }
@game.score.should.be 150
end
def perfect_game_score_should_be_300
(1..12).each { @game.roll(10) }
@game.score.should.be 300
end
end
class Game
def initialize
@rolls = []
end
def roll(pins)
@rolls.push pins
end
def score
compute_score(1, @rolls)
end
def compute_score(frame, rolls)
return 0 if frame > 10
return do_strike(frame, rolls) if strike?(rolls)
return do_spare(frame, rolls) if spare?(rolls)
return do_regular_frame(frame, rolls)
end
def strike?(rolls)
rolls[0] == 10
end
def spare?(rolls)
rolls[0] + rolls[1] == 10
end
def do_strike(frame, rolls)
10 + rolls[1] + rolls[2] + compute_score(frame + 1, rolls[1..-1])
end
def do_spare(frame, rolls)
10 + rolls[2] + compute_score(frame + 1, rolls[2..-1])
end
def do_regular_frame(frame, rolls)
rolls[0] + rolls[1] + compute_score(frame + 1, rolls[2..-1])
end
end
First, thanks to the two of you for your work on this latest version of RSpec. Second, thanks for this example. I tried the bowling game in Ruby with Test::Unit some time ago. I'll compare what I did then with the examples here and see where it leads me.
Notice that David uses
require 'rubygems'
require_gem 'rspec'
while Dave uses
require 'spec'
In my previous experimentation with RSpec and another library available as a gem, I had to use require as David did, even though the example code showed the form that Dave uses. This is true with RSpec v. 0.4.0 for me. I installed it as a gem; I'm using Mac OS X 10.4.5 and Ruby 1.8.4 from DarwinPorts[?]. Any ideas why I can't just do
require 'spec'
instead of
require 'rubygems'
require_gem 'rspec'
Notice that David uses
require 'rubygems'
require_gem 'rspec'
while Dave uses
require 'spec'
In my previous experimentation with RSpec and another library available as a gem, I had to use require as David did, even though the example code showed the form that Dave uses. This is true with RSpec v. 0.4.0 for me. I installed it as a gem; I'm using Mac OS X 10.4.5 and Ruby 1.8.4 from DarwinPorts[?]. Any ideas why I can't just do
require 'spec'
instead of
require 'rubygems'
require_gem 'rspec'
you can avoid having to explicitly require rubygems by putting this in your env var setup:
RUBYOPT=rubygems
It doesn't seem to be ideal, though... but it does work.
IMHO gems is a package & deployment approach... libraries shouldn't depend on it.
Dave
RUBYOPT=rubygems
It doesn't seem to be ideal, though... but it does work.
IMHO gems is a package & deployment approach... libraries shouldn't depend on it.
Dave
We've got a new syntax for specs now. Take a peek at the examples over at http://rspec.rubyforge.org/
Aslak
Aslak
Add Child Page to BowlingWithRspec