I've been writing basic Python scripts for a while now to help process data or automate some task but I've decided I should start picking up unit testing and objective orientated programming (the vast majority of my scripts so far have been procedural).
As a starter I decided to follow along with Uncle Bob's bowling scoring kata to try and get my mind around TDD and the idea of writing the absolute minimal code at every step to either make the test go red or green (plus any refactoring steps).
As it's a bare bones example of following TDD the main program doesn't actually have an entry point other than via the tests.
Things that stand out to my beginner's eye:
There are a lot of
self
s which look like a lot of visual clutter when I read through the code. Is there a better way of doing this? I think it's the density of them that really gets me so I wasn't sure if I could abstract some of them somehow?unittest
seems to have a lot of boilerplate. I had a play withnose
a while back which seemed to strip a lot of that out but I thought it might be a good idea to start properly withunittest
until I have a use case for anything thatnose
(or any other library) offers.
bowling_game.py
#!/usr/bin/env python
class Game:
_rolls = [0] * 21
_current_roll = 0
def roll(self, pins):
self._rolls[self._current_roll] = pins
self._current_roll += 1
def score(self):
score = 0
frame_index = 0
for frame in range(0, 10):
if self._is_strike(frame_index):
score += 10 + self._strike_bonus(frame_index)
frame_index += 1
elif self._is_spare(frame_index):
score += 10 + self._spare_bonus(frame_index)
frame_index += 2
else:
score += self._rolls[frame_index] + self._rolls[frame_index + 1]
frame_index += 2
return score
def _sum_of_balls_in_frame(self, frame_index):
return self._rolls[frame_index] + self._rolls[frame_index + 1]
def _spare_bonus(self, frame_index):
return self._rolls[frame_index + 2]
def _strike_bonus(self, frame_index):
return self._rolls[frame_index + 1] + self._rolls[frame_index + 2]
def _is_spare(self, frame_index):
return self._rolls[frame_index] + self._rolls[frame_index + 1] == 10
def _is_strike(self, frame_index):
return self._rolls[frame_index] == 10
bowling_game_test.py
#!/usr/bin/env python
import unittest
from bowling_game import Game
class BowlingGameTest(unittest.TestCase):
def setUp(self):
self.g = Game()
def roll_many(self, rolls, pins):
for roll in range(0, rolls):
self.g.roll(pins)
def roll_spare(self):
self.g.roll(5)
self.g.roll(5)
def roll_strike(self):
self.g.roll(10)
def test_gutter_game(self):
rolls = 20
pins = 0
self.roll_many(rolls, pins)
self.assertEquals(self.g.score(),0)
def test_all_ones(self):
rolls = 20
pins = 1
self.roll_many(rolls, pins)
self.assertEquals(self.g.score(),20)
def test_one_spare(self):
self.roll_spare()
self.g.roll(3)
self.roll_many(17, 0)
self.assertEquals(self.g.score(),16)
def test_one_strike(self):
self.roll_strike()
self.g.roll(3)
self.g.roll(4)
self.roll_many(16, 0)
self.assertEquals(self.g.score(),24)
def test_perfect_game(self):
self.roll_many(12, 10)
self.assertEquals(self.g.score(),300)
if __name__ == '__main__':
unittest.main()
The commit history is available on GitHub if anyone fancies taking a look at that to see where I might have gone better with the red -> green -> refactor cycle.