Last active
January 10, 2019 05:12
-
-
Save jimweirich/7432160 to your computer and use it in GitHub Desktop.
Bottles of Beer, for Sandi Metz
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Beer | |
# Beer::Number represents a number of bottles on the wall. It uses | |
# MODULUS 100 arithmetic and has special knowledge of verses, | |
# plurals and other song related phrases. | |
class Number | |
def initialize(n) | |
@value = n | |
end | |
def pred | |
Number.new(@value-1) | |
end | |
def to_s | |
@value.to_s | |
end | |
def verse | |
result = "" | |
result << on_the_wall.capitalize << ", " | |
result << of_beer << ".\n" | |
result << action << ", " | |
result << pred.on_the_wall << ".\n" | |
result | |
end | |
protected | |
def on_the_wall | |
"#{of_beer} on the wall" | |
end | |
private | |
def of_beer | |
"#{self} #{bottles} of beer" | |
end | |
def bottles | |
"bottles" | |
end | |
def action | |
"Take #{pronoun} down and pass it around" | |
end | |
def pronoun | |
"one" | |
end | |
class SpecialNumber < Number | |
def self.new | |
super(:ignore) | |
end | |
end | |
class One < SpecialNumber | |
def initialize(_) | |
super(1) | |
end | |
def bottles | |
"bottle" | |
end | |
def pronoun | |
"it" | |
end | |
end | |
class Zero < SpecialNumber | |
def initialize(_) | |
super(0) | |
end | |
def pred | |
Number.new(99) | |
end | |
def to_s | |
"no more" | |
end | |
def action | |
"Go to the store and buy some more" | |
end | |
end | |
CACHE = { | |
0 => Zero.new, | |
1 => One.new, | |
} | |
def self.new(number) | |
CACHE[number] ||= super | |
end | |
end | |
# Beer::Song is the 99 bottles of beer song. | |
class Song | |
def verse(n) | |
Number.new(n).verse | |
end | |
def verses(start=99, finish=0) | |
start.downto(finish).map { |i| verse(i) + "\n" }.join | |
end | |
end | |
end | |
if $0 == __FILE__ | |
puts Beer::Song.new.verses | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'rspec/given' | |
require 'beer_song' | |
module Beer | |
V99 = | |
"99 bottles of beer on the wall, 99 bottles of beer.\n" + | |
"Take one down and pass it around, 98 bottles of beer on the wall.\n" | |
V98 = | |
"98 bottles of beer on the wall, 98 bottles of beer.\n" + | |
"Take one down and pass it around, 97 bottles of beer on the wall.\n" | |
V2 = | |
"2 bottles of beer on the wall, 2 bottles of beer.\n" + | |
"Take one down and pass it around, 1 bottle of beer on the wall.\n" | |
V1 = | |
"1 bottle of beer on the wall, 1 bottle of beer.\n" + | |
"Take it down and pass it around, no more bottles of beer on the wall.\n" | |
V0 = | |
"No more bottles of beer on the wall, no more bottles of beer.\n" + | |
"Go to the store and buy some more, 99 bottles of beer on the wall.\n" | |
describe Number do | |
Given(:bn) { Number.new(n) } | |
Invariant { bn.pred == Number.new((n+99) % 100) } | |
context "with 99" do | |
Given(:n) { 99 } | |
Then { bn.to_s == '99' } | |
Then { bn.verse == V99 } | |
end | |
context "with 98" do | |
Given(:n) { 98 } | |
Then { bn.to_s == '98' } | |
Then { bn.verse == V98 } | |
end | |
context "with 2" do | |
Given(:n) { 2 } | |
Then { bn.to_s == '2' } | |
Then { bn.verse == V2 } | |
end | |
context "with 1" do | |
Given(:n) { 1 } | |
Then { bn.to_s == '1' } | |
Then { bn.verse == V1 } | |
end | |
context "with 0" do | |
Given(:n) { 0 } | |
Then { bn.to_s == 'no more' } | |
Then { bn.verse == V0 } | |
end | |
end | |
describe Song do | |
Given(:bottle) { Song.new } | |
def count(string, pattern) | |
string.scan(Regexp.new(Regexp.quote(pattern))).size | |
end | |
context "one verse" do | |
When(:result) { bottle.verse(2) } | |
Then { result == V2 } | |
end | |
context "entire song" do | |
When(:result) { bottle.verses(99) } | |
Then { count(result, V99 + "\n") == 1 } | |
Then { count(result, V98 + "\n") == 1 } | |
Then { count(result, V2 + "\n") == 1 } | |
Then { count(result, V1 + "\n") == 1 } | |
Then { count(result, V0 + "\n") == 1 } | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'minitest/autorun' | |
require 'minitest/pride' | |
require './beer_song' | |
class BeerSongTest < Minitest::Test | |
def beer_song | |
@beer_song = Beer::Song.new | |
end | |
def teardown | |
@beer_song = nil | |
end | |
def test_a_typical_verse | |
expected = | |
"8 bottles of beer on the wall, 8 bottles of beer.\n" + | |
"Take one down and pass it around, 7 bottles of beer on the wall.\n" | |
assert_equal expected, beer_song.verse(8) | |
end | |
def test_another_typical_verse | |
expected = | |
"3 bottles of beer on the wall, 3 bottles of beer.\n" + | |
"Take one down and pass it around, 2 bottles of beer on the wall.\n" | |
assert_equal expected, beer_song.verse(3) | |
end | |
def test_verse_1 | |
expected = | |
"1 bottle of beer on the wall, 1 bottle of beer.\n" + | |
"Take it down and pass it around, no more bottles of beer on the wall.\n" | |
assert_equal expected, beer_song.verse(1) | |
end | |
def test_verse_2 | |
expected = | |
"2 bottles of beer on the wall, 2 bottles of beer.\n" + | |
"Take one down and pass it around, 1 bottle of beer on the wall.\n" | |
assert_equal expected, beer_song.verse(2) | |
end | |
def test_verse_0 | |
expected = | |
"No more bottles of beer on the wall, no more bottles of beer.\n" + | |
"Go to the store and buy some more, 99 bottles of beer on the wall.\n" | |
assert_equal expected, beer_song.verse(0) | |
end | |
def test_several_verses | |
expected = | |
"8 bottles of beer on the wall, 8 bottles of beer.\n" + | |
"Take one down and pass it around, 7 bottles of beer on the wall.\n\n" + | |
"7 bottles of beer on the wall, 7 bottles of beer.\n" + | |
"Take one down and pass it around, 6 bottles of beer on the wall.\n\n" + | |
"6 bottles of beer on the wall, 6 bottles of beer.\n" + | |
"Take one down and pass it around, 5 bottles of beer on the wall.\n\n" | |
assert_equal expected, beer_song.verses(8, 6) | |
end | |
def test_all_the_rest_of_the_verses | |
expected = | |
"3 bottles of beer on the wall, 3 bottles of beer.\n" + | |
"Take one down and pass it around, 2 bottles of beer on the wall.\n" + | |
"\n" + | |
"2 bottles of beer on the wall, 2 bottles of beer.\n" + | |
"Take one down and pass it around, 1 bottle of beer on the wall.\n" + | |
"\n" + | |
"1 bottle of beer on the wall, 1 bottle of beer.\n" + | |
"Take it down and pass it around, no more bottles of beer on the wall.\n" + | |
"\n" + | |
"No more bottles of beer on the wall, no more bottles of beer.\n" + | |
"Go to the store and buy some more, 99 bottles of beer on the wall.\n" + | |
"\n" | |
assert_equal expected, beer_song.verses(3) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'rake/clean' | |
require 'rake/testtask' | |
task :default => [:spec, :test] | |
task :spec do | |
sh "rspec ." | |
end | |
task :test do | |
ruby "beer_song_test.rb" | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment