Wednesday, June 29, 2011

Learning Python: #5 - Finish Bowling Game Kata

My finished test class TestGame.py:
import unittest
import Game

class TestGame(unittest.TestCase):

    def setUp(self):
        self.game = Game.Game()

    def testGutterGameHasZeroScore(self):
        for i in range(20):
            self.game.roll(0)
        assert self.game.score() == 0, "Score is zero"
  
    def testGameAllOnesHasScoreOfTwenty(self):
        for i in range(20):
            self.game.roll(1)
        assert self.game.score() == 20
        
    def testGameThrowsExceptionWithMoreThanTwentyOneRolls(self):
        try:
            for i in range(22):
                self.game.roll(10)
        except ValueError:
            pass
        else:
            self.fail("Expected a ValueError")
            
    def testGameWithOneSpare(self):
        self.game.roll(1)
        self.game.roll(9)
        self.game.roll(5)
        
        for i in range(17):
            self.game.roll(0)
        
        assert self.game.score() == 20
        
    def testGameWithOneStrike(self):
        self.game.roll(10)
        self.game.roll(1)
        self.game.roll(1)
        
        for i in range(17):
            self.game.roll(0)
        
        assert self.game.score() == 14
            
    def testPerfectGameScores300(self):
        for i in range(12):
            self.game.roll(10)
        assert self.game.score() == 300, "Perfect Game should score 300"
         
if __name__ == "__main__":
    unittest.main()

My finished Game.py:
MAX_ROLLS = 21
FRAMES = 10
STRIKE = 10
SPARE = 10
    
class Game:
     
    def __init__(self):
        self.scoreArr = []
        
    def roll(self, pins):
        if self.numberOfRollsTaken() > MAX_ROLLS:
            raise ValueError
        self.scoreArr.append(pins)
        
    def score(self):
        score = 0
        currentRoll = 0
        frame = 0
        while frame < FRAMES:
            if self.scoreArr[currentRoll] == STRIKE:
                score += self.scoreNextTwoBalls(currentRoll)
                currentRoll += 1
            elif self.frameScore(currentRoll) == SPARE:
                score += self.scoreNextTwoBalls(currentRoll)
                currentRoll += 2
            else:
                score += self.frameScore(currentRoll)
                currentRoll += 2
                
            frame = frame + 1
        return score

    def numberOfRollsTaken(self):
        return len(self.scoreArr) +1

    def scoreNextTwoBalls(self, index):
        return self.scoreArr[index] + self.scoreArr[index + 1] + self.scoreArr[index + 2]
        
    def frameScore(self, index):
        return self.scoreArr[index] + self.scoreArr[index + 1]
A couple of things that I learned while doing this Bowling Game kata:
  • It appears that it is a requirement for test methods to start with the word 'test' or PyUnit will not recognize them as tests that need to be run.
  • A variable and a method in the same class cannot both have the same name, or at least my syntax was wrong
  • The ':' at the end of class/method definitions and control statements gets me almost everytime; I cannot seem to remember that it needs to be there

Tuesday, June 28, 2011

Learning Python: #4 - UnitTesting - Second Test

Now that I have the first test completed, I can move on to adding some actual functionality to the Game class. My next step was to have it keep track of the number of pins rolled. Here is my second test:
def testGameHasScoreOfOneWithFirstRollOfOnePin(self):
    game = Game()
    game.roll(1)
    assert game.score == 1
At this point the tests no longer pass because the Game class does not have a roll method. My first attempt to add the roll method to my Game class failed:
def roll(pins):
        score += pins
Here is the error that was printed:

E.
======================================================================
ERROR: testGameHasScoreOfOneWithFirstRollOfOnePin (__main__.TestGame)
----------------------------------------------------------------------
Traceback (most recent call last):
File "TestGame.py", line 13, in testGameHasScoreOfOneWithFirstRollOfOnePin
game.roll(1)
TypeError: roll() takes exactly 1 positional argument (2 given)

----------------------------------------------------------------------
Ran 2 tests in 0.002s

FAILED (errors=1)

This is a very confusing error when you are unfamiliar with the language. Clearly I am passing in one argument to a method that takes one argument. What in the world?? The way that methods work in Python is that the object is passed as the first argument of the function. So essentially, the method call acts like game.roll(game, 1) though it is not actually called this way. What is needed is to change the method definition. My updated roll method:
def roll(self, pins):
    self.score += pins
Both of my tests now pass:
..
----------------------------------------------------------------------
Ran 2 tests in 0.002s

OK

Learning Python: #3 - UnitTesting - First Test

I was struggling with something to write to help me learn Python and I decided to do the classic bowling game example. I did a quick search to find out how to write TDD with Python and came across this PowerPoint slide which helped me get started writing the tests.

From that ppt slide, I figured out what my test file and first test should look like.

TestGame.py:
import unittest

class TestGame(unittest.TestCase):
    pass
    
if __name__ == "__main__":
    unittest.main()
and my First Test:
import unittest

class TestGame(unittest.TestCase):

    def testGameHasZeroScore(self):
        game = Game()
        assert game.score == 0, "Score is zero"

if __name__ == "__main__":
    unittest.main()
    
At this point I am still not certain what all of this is doing. Running the test produces this output:

E
======================================================================
ERROR: testGameHasZeroScore (__main__.TestGame)
----------------------------------------------------------------------
Traceback (most recent call last):
File "TestGame.py", line 7, in testGameHasZeroScore
game = Game()
NameError: global name 'Game' is not defined

----------------------------------------------------------------------
Ran 1 test in 0.002s

FAILED (errors=1)

This is good then because I know that I'm using the unittest import correctly. At this point I wasn't familiar with why self was being passed to the test method but I assumed it had a need. I found out later why it was needed. To get this test to pass I had to create a Game.py file that created my Game class with a score member, and I had to add from Game import Game to my TestGame.py file under the current import statement. Here is my Game class:

Game.py:
class Game: 
    score = 0

And my TestGame.py:
import unittest
from Game import Game

class TestGame(unittest.TestCase):

    def testGameHasZeroScore(self):
        game = Game()
        assert game.score == 0, "Score is zero"

if __name__ == "__main__":
    unittest.main()

Running this now produces this output:

.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

Awesome! I have completed my first unit test with Python.

Monday, June 27, 2011

Learning Python: #2 - Five Interesting Things

So, I'm going through the tutorial and have already found some things that I find interesting.
  1. Division Operators
    • 3/2 = 1.5
    • 3//2 = 1 - division that produces an integer by discarding the remainder
  2. The _ variable will have the last thing printed in the window assigned to it
  3. A \ at the end of a line will let the string literal span multiple lines
  4. Indexing of strings
    • "someString"[2:7] prints 'meStr'
    • "someString"[:3] prints 'som'
    • "someString"[-3:] prints 'ing'
    • "someString"[:-3] prints 'someStr'
  5. The built in function len() applies to both strings and lists
Bonus:
  • By default the print() function will end with a newline. You can specify a different ending with the keyword end
    • Syntax: print(someVariable, end="|")
    • The above used in a loop will create a pipe delimited list

Learning Python: #1 - Hello World

My company provides four hours of free time per week to spend time learning a new technology, language, etc. I've decided to take some time to learn Python and sort of document what I learn as I go along.

I downloaded the latest Python for Windows (3.2 as of the time of this post) and started reading the documentation (Here is a link to a tutorial for Python 3.2.). It was easy to get Python downloaded and setup to run from the cmd window in Windows 7. All that I needed to do after downloading Python was to add the install directory to the Path Environment variable.

When I'm learning a language it typically does not take long to learn the syntax to make a Hello World application; however, it took me a little bit longer than normal with Python. What took me so long with Python and what is important to note is that the syntax has changed since 2.7.2, which is the version of the documentation I happened to be reading. I was typing >>> print "Hello World" but for 3.2 it should have been >>> print ("Hello World") with the text wrapped in parens.

The first gives an error 'File "<stdin>", line 1' while the second does what is expected and actually prints out the text of 'Hello World'.

I'm eager to put my free time to use learning something and growing as a developer.