Skip to content

Commit c980e66

Browse files
committed
Replace hard-coded Bowling tests with test generator (exercism#456)
Also updated the example solution to address new test case
1 parent 34baa63 commit c980e66

File tree

5 files changed

+157
-135
lines changed

5 files changed

+157
-135
lines changed

exercises/bowling/.version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2

exercises/bowling/bowling_test.rb

Lines changed: 108 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -3,224 +3,199 @@
33
require 'minitest/autorun'
44
require_relative 'bowling'
55

6-
class GameTest < Minitest::Test
6+
# Test data version:
7+
# 0a51cfc
8+
class BowlingTest < Minitest::Test
79
def setup
810
@game = Game.new
911
end
1012

11-
def test_must_be_able_to_roll_with_number_of_pins
12-
assert_respond_to @game, :roll
13-
assert_equal 1, @game.method(:roll).arity
13+
def roll(rolls)
14+
rolls.each { |pins| @game.roll(pins) }
1415
end
1516

16-
def test_must_have_a_score
17-
skip
18-
assert_respond_to @game, :score
17+
def test_should_be_able_to_score_a_game_with_all_zeros
18+
roll([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
19+
assert_equal 0, @game.score
1920
end
2021

21-
def test_should_be_able_to_score_open_frame
22+
def test_should_be_able_to_score_a_game_with_no_strikes_or_spares
2223
skip
23-
@game.roll(3)
24-
@game.roll(4)
25-
roll_n_times(18, 0)
26-
assert_equal 7, @game.score
24+
roll([3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6])
25+
assert_equal 90, @game.score
2726
end
2827

29-
def test_should_be_able_to_score_multiple_frames
28+
def test_a_spare_followed_by_zeros_is_worth_ten_points
3029
skip
31-
[3, 4, 2, 3, 5, 2].each do |pins|
32-
@game.roll pins
33-
end
34-
roll_n_times(14, 0)
35-
assert_equal 19, @game.score
30+
roll([6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
31+
assert_equal 10, @game.score
3632
end
3733

38-
def test_should_score_a_game_with_all_gutterballs
34+
def test_points_scored_in_the_roll_after_a_spare_are_counted_twice
3935
skip
40-
roll_n_times(20, 0)
41-
assert_equal 0, @game.score
36+
roll([6, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
37+
assert_equal 16, @game.score
4238
end
4339

44-
def test_should_score_a_game_with_all_single_pin_rolls
40+
def test_consecutive_spares_each_get_a_one_roll_bonus
4541
skip
46-
roll_n_times(20, 1)
47-
assert_equal 20, @game.score
42+
roll([5, 5, 3, 7, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
43+
assert_equal 31, @game.score
4844
end
4945

50-
def test_should_allow_game_with_all_open_frames
46+
def test_a_spare_in_the_last_frame_gets_a_one_roll_bonus_that_is_counted_once
5147
skip
52-
roll_n_times(10, [3, 6])
53-
assert_equal 90, @game.score
48+
roll([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 7])
49+
assert_equal 17, @game.score
5450
end
5551

56-
def test_should_correctly_score_a_strike_that_is_not_on_the_last_frame
52+
def test_a_strike_earns_ten_points_in_frame_with_a_single_roll
5753
skip
58-
@game.roll(10)
59-
@game.roll(5)
60-
@game.roll(3)
61-
roll_n_times(16, 0)
62-
63-
assert_equal 26, @game.score
54+
roll([10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
55+
assert_equal 10, @game.score
6456
end
6557

66-
def test_should_score_a_spare_that_is_not_on_the_last_frame
58+
def test_points_scored_in_the_two_rolls_after_a_strike_are_counted_twice_as_a_bonus
6759
skip
68-
@game.roll(5)
69-
@game.roll(5)
70-
@game.roll(3)
71-
@game.roll(4)
72-
roll_n_times(16, 0)
73-
74-
assert_equal 20, @game.score
60+
roll([10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
61+
assert_equal 26, @game.score
7562
end
7663

77-
def test_should_score_multiple_strikes_in_a_row
64+
def test_consecutive_strikes_each_get_the_two_roll_bonus
7865
skip
79-
@game.roll(10)
80-
@game.roll(10)
81-
@game.roll(10)
82-
@game.roll(5)
83-
@game.roll(3)
84-
roll_n_times(12, 0)
85-
66+
roll([10, 10, 10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
8667
assert_equal 81, @game.score
8768
end
8869

89-
def test_should_score_multiple_spares_in_a_row
70+
def test_a_strike_in_the_last_frame_gets_a_two_roll_bonus_that_is_counted_once
9071
skip
91-
@game.roll(5)
92-
@game.roll(5)
93-
@game.roll(3)
94-
@game.roll(7)
95-
@game.roll(4)
96-
@game.roll(1)
97-
roll_n_times(14, 0)
98-
99-
assert_equal 32, @game.score
72+
roll([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 1])
73+
assert_equal 18, @game.score
10074
end
10175

102-
def test_should_allow_fill_balls_when_the_final_frame_is_strike
76+
def test_rolling_a_spare_with_the_two_roll_bonus_does_not_get_a_bonus_roll
10377
skip
104-
roll_n_times(18, 0)
105-
@game.roll(10)
106-
@game.roll(7)
107-
@game.roll(1)
108-
109-
assert_equal 18, @game.score
78+
roll([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 3])
79+
assert_equal 20, @game.score
11080
end
11181

112-
def test_should_allow_fill_ball_in_last_frame_if_spare
82+
def test_strikes_with_the_two_roll_bonus_do_not_get_bonus_rolls
11383
skip
114-
roll_n_times(18, 0)
115-
@game.roll(9)
116-
@game.roll(1)
117-
@game.roll(7)
118-
119-
assert_equal 17, @game.score
84+
roll([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10])
85+
assert_equal 30, @game.score
12086
end
12187

122-
def test_should_allow_fill_balls_to_be_strike
88+
def test_a_strike_with_the_one_roll_bonus_after_a_spare_in_the_last_frame_does_not_get_a_bonus
12389
skip
124-
roll_n_times(18, 0)
125-
@game.roll(10)
126-
@game.roll(10)
127-
@game.roll(10)
128-
129-
assert_equal 30, @game.score
90+
roll([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 10])
91+
assert_equal 20, @game.score
13092
end
13193

132-
def test_should_score_a_perfect_game
94+
def test_all_strikes_is_a_perfect_game
13395
skip
134-
roll_n_times(12, 10)
96+
roll([10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10])
13597
assert_equal 300, @game.score
13698
end
13799

138-
def test_should_not_allow_rolls_with_negative_pins
100+
def test_rolls_can_not_score_negative_points
139101
skip
140-
assert_raises(
141-
RuntimeError,
142-
'Pins must have a value from 0 to 10') do
143-
@game.roll(-1)
144-
end
102+
assert_raises StandardError do
103+
roll([-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
104+
@game.score
105+
end
145106
end
146107

147-
def test_should_not_allow_rolls_better_than_strike
108+
def test_a_roll_can_not_score_more_than_10_points
148109
skip
149-
assert_raises(
150-
RuntimeError,
151-
'Pins must have a value from 0 to 10') do
152-
@game.roll(11)
153-
end
110+
assert_raises StandardError do
111+
roll([11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
112+
@game.score
113+
end
154114
end
155115

156-
def test_should_not_allow_two_normal_rolls_better_than_strike
116+
def test_two_rolls_in_a_frame_can_not_score_more_than_10_points
157117
skip
158-
assert_raises RuntimeError, 'Pin count exceeds pins on the lane' do
159-
@game.roll(5)
160-
@game.roll(6)
118+
assert_raises StandardError do
119+
roll([5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
120+
@game.score
161121
end
162122
end
163123

164-
def test_should_not_allow_two_normal_rolls_better_than_strike_in_last_frame
124+
def test_two_bonus_rolls_after_a_strike_in_the_last_frame_can_not_score_more_than_10_points
165125
skip
166-
roll_n_times(18, 0)
167-
assert_raises RuntimeError, 'Pin count exceeds pins on the lane' do
168-
@game.roll(10)
169-
@game.roll(5)
170-
@game.roll(6)
126+
assert_raises StandardError do
127+
roll([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 5, 6])
128+
@game.score
171129
end
172130
end
173131

174-
def test_should_not_allow_to_take_score_at_the_beginning
132+
def test_an_unstarted_game_can_not_be_scored
175133
skip
176-
assert_raises(
177-
RuntimeError,
178-
'Score cannot be taken until the end of the game',
179-
) do
134+
assert_raises StandardError do
135+
roll([])
180136
@game.score
181137
end
182138
end
183139

184-
def test_should_not_allow_to_take_score_before_game_has_ended
140+
def test_an_incomplete_game_can_not_be_scored
185141
skip
186-
roll_n_times(19, 5)
187-
assert_raises(
188-
RuntimeError,
189-
'Score cannot be taken until the end of the game') do
190-
@game.score
191-
end
142+
assert_raises StandardError do
143+
roll([0, 0])
144+
@game.score
145+
end
192146
end
193147

194-
def test_should_not_allow_rolls_after_the_tenth_frame
148+
def test_a_game_with_more_than_ten_frames_can_not_be_scored
195149
skip
196-
roll_n_times(20, 0)
197-
assert_raises(
198-
RuntimeError,
199-
'Should not be able to roll after game is over',
200-
) do
201-
@game.roll(0)
150+
assert_raises StandardError do
151+
roll([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
152+
@game.score
202153
end
203154
end
204155

205-
def test_should_not_calculate_score_before_fill_balls_have_been_played
156+
def test_bonus_rolls_for_a_strike_in_the_last_frame_must_be_rolled_before_score_can_be_calculated
206157
skip
207-
roll_n_times(10, 10)
158+
assert_raises StandardError do
159+
roll([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10])
160+
@game.score
161+
end
162+
end
208163

209-
assert_raises RuntimeError, 'Game is not yet over, cannot score!' do
164+
def test_both_bonus_rolls_for_a_strike_in_the_last_frame_must_be_rolled_before_score_can_be_calculated
165+
skip
166+
assert_raises StandardError do
167+
roll([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10])
210168
@game.score
211169
end
212170
end
213171

214-
def roll_n_times(rolls, pins)
215-
rolls.times do
216-
Array(pins).each { |value| @game.roll(value) }
172+
def test_bonus_roll_for_a_spare_in_the_last_frame_must_be_rolled_before_score_can_be_calculated
173+
skip
174+
assert_raises StandardError do
175+
roll([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3])
176+
@game.score
217177
end
218178
end
219-
private :roll_n_times
220179

221-
# Don't forget to define a constant VERSION inside of BookKeeping.
180+
# Problems in exercism evolve over time, as we find better ways to ask
181+
# questions.
182+
# The version number refers to the version of the problem you solved,
183+
# not your solution.
184+
#
185+
# Define a constant named VERSION inside of the top level BookKeeping
186+
# module, which may be placed near the end of your file.
187+
#
188+
# In your file, it will look like this:
189+
#
190+
# module BookKeeping
191+
# VERSION = 1 # Where the version number matches the one in the test.
192+
# end
193+
#
194+
# If you are curious, read more about constants on RubyDoc:
195+
# http://ruby-doc.org/docs/ruby-doc-bundle/UsersGuide/rg/constants.html
196+
222197
def test_bookkeeping
223198
skip
224-
assert_equal 1, BookKeeping::VERSION
199+
assert_equal 2, BookKeeping::VERSION
225200
end
226201
end

exercises/bowling/example.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module BookKeeping
2-
VERSION = 1
2+
VERSION = 2
33
end
44

55
class Game
@@ -28,7 +28,7 @@ def validate(pins)
2828
def valid_frame?(pins)
2929
last_roll_was_strike = @score_card[current_frame].last == 10
3030

31-
(last_frame? && last_roll_was_strike) ||
31+
(last_frame? && last_roll_was_strike || spare?) ||
3232
@score_card[current_frame].last.to_i + pins <= RULES[:MAX]
3333
end
3434

exercises/bowling/example.tt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env ruby
2+
gem 'minitest', '>= 5.0.0'
3+
require 'minitest/autorun'
4+
require_relative 'bowling'
5+
6+
# Test data version:
7+
# <%= sha1 %>
8+
class BowlingTest < Minitest::Test
9+
def setup
10+
@game = Game.new
11+
end
12+
13+
def roll(rolls)
14+
rolls.each { |pins| @game.roll(pins) }
15+
end
16+
<% test_cases.each do |test_case| %>
17+
def <%= test_case.name %><% if test_case.skipped? %>
18+
skip<% end %>
19+
<% if test_case.expected == -1 %>assert_raises StandardError do
20+
roll(<%= test_case.rolls %>)
21+
@game.score
22+
end<% else %>roll(<%= test_case.rolls %>)
23+
assert_equal <%= test_case.expected %>, @game.score<% end %>
24+
end
25+
<% end %>
26+
<%= IO.read(XRUBY_LIB + '/bookkeeping.md') %>
27+
def test_bookkeeping
28+
skip
29+
assert_equal <%= version.next %>, BookKeeping::VERSION
30+
end
31+
end

lib/bowling_cases.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class BowlingCase < OpenStruct
2+
def name
3+
'test_%s' % description.downcase.tr(' ', '_')
4+
end
5+
6+
def skipped?
7+
index > 0
8+
end
9+
end
10+
11+
BowlingCases = proc do |data|
12+
JSON.parse(data)['score']['cases'].map.with_index do |row, i|
13+
BowlingCase.new(row.merge('index' => i))
14+
end
15+
end

0 commit comments

Comments
 (0)