Python DevCenter
oreilly.comSafari Books Online.Conferences.


More Test-Driven Development in Python

by Jason Diamond

In the first article in this series, Test-Driven Development with Python, Part 1, I started to build an event tracking application using the principle of test-driven development. Before writing any new code or making changes to any existing code, I wrote a failing test first. When the new test started passing, I stopped and moved on. It's a simple technique, but requires discipline in order to apply it successfully.

If you haven't read part one yet, I suggest you do so before continuing on in this article. That will familiarize you with the problem I'm trying to solve and bring you up to speed on my current predicament.

A New Design Emerges

As indicated in the previous article, I'm starting to think that the single DatePattern class I have so far is responsible for too much. It's only 30 lines long at this point, but I don't mean its length when I say "too much." What I mean is, conceptually, it does four different things; I even started adding a fifth before I stopped myself.

What are its four different responsibilities? It has to determine if the year matches. That's one. The month is another, as are the day and the weekday, too. Can I break those responsibilities into four different classes? I'm sure I can, but should I?

Not only that, but for each component of the pattern, I have to check to see if it's a wild card and act appropriately. Maybe I need yet another class to encapsulate that logic.

I can't answer these questions by just thinking about them, so I will explore by writing a few tests:

class NewTests(unittest.TestCase):

    def testYearMatches(self):
        yp = YearPattern(2004)
        d =, 9, 29)


Note that I've put this new test case in a new fixture. If this turns out to be a dead end, I can easily delete this fixture and continue onward with the old. If this ends up being a good idea, I can just as easily delete the old fixture (and the code it exercised, the DatePattern class).

This new test case, of course, fails. Here's the code required to make it pass:

class YearPattern:
    def __init__(self, year):

    def matches(self, date):
        return True


Boring. How about another test?

def testYearDoesNotMatch(self):
    yp = YearPattern(2003)
    d =, 9, 29)


Now to make the new test pass without breaking the old one:

class YearPattern: 
    def __init__(self, year):
        self.year = year

    def matches(self, date):
        return self.year == date.year



What's the point? Before I can show you that, I need to write a few more tests:

def testMonthMatches(self):
    mp = MonthPattern(9)
    d =, 9, 29)

def testMonthDoesNotMatch(self):
    mp = MonthPattern(8)
    d =, 9, 29)

def testDayMatches(self):
    dp = DayPattern(29)
    d =, 9, 29)

def testDayDoesNotMatch(self):
    dp = DayPattern(28)
    d =, 9, 29)

def testWeekdayMatches(self):
    wp = WeekdayPattern(2) # Wednesday
    d =, 9, 29)

def testWeekdayDoesNotMatch(self):
    wp = WeekdayPattern(1) # Tuesday
    d =, 9, 29)


Here's the code to make these tests pass:

class MonthPattern:
    def __init__(self, month):
        self.month = month

    def matches(self, date):
        return self.month == date.month

class DayPattern:
    def __init__(self, day): = day

    def matches(self, date):
        return ==

class WeekdayPattern:
    def __init__(self, weekday):
        self.weekday = weekday

    def matches(self, date):
        return self.weekday == date.weekday()


Is it obvious where I'm going with this yet? If not, this test should make it clear:

def testCompositeMatches(self):
    cp = CompositePattern()
    d =, 9, 29)


What I've stumbled across here is an instance of the Composite Pattern. (Interestingly, this is the same name as my class--that wasn't intentional, I promise.)

Pages: 1, 2, 3

Next Pagearrow

Sponsored by: