Week 13: Unit testing and Object-Oriented design

Lectures:

You must be logged on to your swarthmore.edu account for the youtube lecture videos to be viewable.

Unit testing

It is crucial that your programs behave as you intend them to. Unit testing is a method of building tests right into the program itself. Every time you make a change to the program the unit tests will ensure that you haven’t broken something in the code.

In Python, we can use assert to implement unit tests. An assertion is an expression that you expect to evaluate to True. If it is true, as expected, then nothing happens. However, if it is not true, then an assertion error will be raised. Consider the following class definition and accompanying main program that uses assert to create a set of unit tests.

class Team(object):
    def __init__(self, name, sport):
        self.name = name
        self.sport = sport
        self.wins = 0
        self.losses = 0
    def __str__(self):
        return "Team:%s Wins:%d Losses:%d" % \
            (self.name, self.wins, self.losses)
    def getName(self):
        return self.name
    def getWins(self):
        return self.wins
    def getLosses(self):
        return self.losses
    def wonGame(self):
        self.wins += 1
    def lostGame(self):
        self.losses += 1

def main():
    print("Unit testing the Team class...")
    t = Team("SwatCS", "ICPC")
    assert(t.getWins() == 0)
    assert(t.getLosses() == 0)
    assert(t.getName() == "SwatCS")
    t.wonGame()
    t.wonGame()
    t.lostGame()
    assert(t.getWins() == 2)
    assert(t.getLosses() == 1)
    print(t)
    print("Team class passed all unit tests!")

if __name__ == '__main__':
    main()

The special if statement at the bottom of the program will only execute the main program when you run this file directly like this: python3 team.py. If instead you import this file, the main program will not be executed. This is one way to build unit testing right into the program itself.

Here’s the output you would see from running this program:

Unit testing the Team class...
Team:SwatCS Wins:2 Losses:1
Team class passed all unit tests!

Suppose we had a mistake in our implementation. For example, perhaps in creating the lostGame() method we accidentally decremented self.wins instead of incrementing self.losses. Now when we run this program we would see the following output:

Unit testing the Team class...
Traceback (most recent call last):
  File "team.py", line 36, in <module>
    main()
  File "team.py", line 30, in main
    assert(t.getWins() == 2)
AssertionError

The AssertionError will interrupt the program execution immediately, and indicate the line number where the problem occurred.

Object-Oriented Design

Now that you understand the syntax and semantics of creating your own classes in Python we can think more broadly about the process of object-oriented design.

A common rule of thumb in object-oriented design is that for each individual type of object in the real world your program should have a corresponding class defined. One way to go about the design process is to write down a description of your problem. The nouns in that description will likely correspond with the classes you’ll need and the verbs in that description will likely correspond with methods you’ll need.

Let’s create the infrastructure we need to implement a card game such as Blackjack.

Card games typically use a deck of 52 cards. Each card has one of 4 suits (clubs, diamonds, hearts, or spades) and one of 13 ranks (ace, 2, 3, …​, 10, jack, queen, or king). Most games start by shuffling the deck and then deal out hands to each player.

Based on this description, we should create classes for Card, Deck, and Hand. We should also have methods for dealing and shuffling.

Do an update21 to get all of the files you’ll need to play Blackjack. Start by adding unit tests into the file cards.py that contains class definitions for both Card and Deck.