ArticleS
.
DavidChelimsky
.
BowlingWithRspec
Edit Page:
!title Bowling with rSpec Now THIS is fun. Here's the bowling game test driven in Ruby with the new [[rspec (v0.5)][http://rspec.rubyforge.org]]. !******** Spec using 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}}} **********! !********> Spec using v0.4 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}}} **********! !*************> Solution 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}}} *************! !commentForm !* Fri, 3 Mar 2006 16:27:14, Dave Astels, A recursive solution 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 }}} *! !* Sat, 4 Mar 2006 18:02:18, Craig Demyanovich, Thanks and using require 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][http://www.darwinports.org/]]. Any ideas why I can't just do require 'spec' instead of require 'rubygems' require_gem 'rspec' *! !* Sun, 5 Mar 2006 15:30:52, Dave Astels, require 'rubygems' 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 *! !* Wed, 22 Mar 2006 18:25:59, ${micah}, Man, that's small! The amount of code I mean. Ruby rocks! *! !* Sat, 15 Apr 2006 10:10:54, Aslak Hellesoy, Update the example - the syntax has changed! We've got a new syntax for specs now. Take a peek at the examples over at [[http://rspec.rubyforge.org/][http://rspec.rubyforge.org/]] Aslak *!
Hints:
Use alt+s (Windows) or control+s (Mac OS X) to save your changes. Or, tab from the text area to the "Save" button!
Grab the lower-right corner of the text area to increase its size (works with some browsers).