text: Content
  All Articles
  Python News
  Numerically Python
  Python & XML
  Community
  Database
  Distributed
  Education
  Getting Started
  Graphics
  Internet
  OS
  Programming
  Scientific
  Tools
  Tutorials
  User Interfaces
  ONLamp Subjects
  Linux
  Apache
  MySQL
  Perl
  PHP
  Python
  BSD
  More Test-Driven Development in Python
  Pages: 1, 2, 3
  Because it's so easy and fun, I want to add another pattern:
  class NthWeekdayPatternTests(unittest.TestCase):
  def setUp(self):
  self.pattern = NthWeekdayPattern(1, WEDNESDAY)
  def testMatches(self):
  firstWedOfSep2004 = datetime.date(2004, 9, 1)
  self.failUnless(self.pattern.matches(firstWedOfSep2004))
  def testNotMatches(self):
  secondWedOfSep2004 = datetime.date(2004, 9, 8)
  self.failIf(self.pattern.matches(secondWedOfSep2004))
  I don't have an example of this in my use cases as listed at the beginning
  of this article, but it's a feature that both calendar and pal support, so I
  expected to add it at some point.
  Making these tests pass shouldn't be too hard:
  class NthWeekdayPattern:
  def __init__(self, n, weekday):
  self.n = n
  self.weekday = weekday
  def matches(self, date):
  if self.weekday != date.weekday():
  return False
  n = 1
  while True:
  previousDate = date - datetime.timedelta(7 * n)
  if previousDate.month == date.month:
  n += 1
  else:
  break
  return self.n == n
  OK, it was harder than I thought. I'm not a huge fan of the way that
  algorithm looks, but it's OK for now. I really should at least extract it into
  its own method so I can give it an intention-revealing name:
  def matches(self, date):
  if self.weekday != date.weekday():
  return False
  return self.n == self.getWeekdayNumber(date)
  def getWeekdayNumber(self, date):
  n = 1
  while True:
  previousDate = date - datetime.timedelta(7 * n)
  if previousDate.month == date.month:
  n += 1
  else:
  break
  return n
  Better.
  The last example I do have at the beginning of this article is the "last day
  of the month" case. That should match days "in reverse." I could modify the
  existing DayPattern class, but instead I want to add a new pattern
  class:
  DayPattern
  class LastDayInMonthPatternTests(unittest.TestCase):
  def testMatches(self):
  lastDayInSep2004 = datetime.date(2004, 9, 30)
  pattern = LastDayInMonthPattern()
  self.failUnless(pattern.matches(lastDayInSep2004))
  While typing in that test, I decided that I couldn't think of a reason to
  support the second-to-last day in a month, or the third-to-last day, and so on.
  Can you? I made it easy on myself and decided to implement a class called
  LastDayInMonthPattern. People usually argue that writing tests up
  front takes too much work, but writing a test first this time actually saved me
  from writing code I would never use!
  LastDayInMonthPattern
  The implementation of this new pattern is:
  class LastDayInMonthPattern:
  def matches(self, date):
  tomorrow = date + datetime.timedelta(1)
  return tomorrow.month != date.month
  Nice.
  Although I just realized I'm cheating again. Here's how the fixture should
  have looked (after extracting out the setUp method) before fully
  implementing the matches method:
  setUp
  matches
  class LastDayInMonthPatternTests(unittest.TestCase):
  def setUp(self):
  self.pattern = LastDayInMonthPattern()
  def testMatches(self):
  lastDayInSep2004 = datetime.date(2004, 9, 30)
  self.failUnless(self.pattern.matches(lastDayInSep2004))
  def testNotMatches(self):
  secondToLastDayInSep2004 = datetime.date(2004, 9, 29)
  self.failIf(self.pattern.matches(secondToLastDayInSep2004))
  I feel pretty good about myself right now. That usually means it's time to
  refactor. Now I really want to do some renaming.
  For the last pattern I implemented, I was very explicit about what it did:
  LastDayInMonthPattern only matches the last day in a month and
  there's no further clarification needed. What about
  NthWeekdayPattern and LastWeekdayPattern? I really
  want to add InMonth to the end of both of those class names. Yes,
  I'm that picky.
  NthWeekdayPattern
  LastWeekdayPattern
  InMonth
  Related Reading
  Learning Python
  By Mark Lutz, David Ascher
  I also took this time to rename a few of the test cases and reorder some of
  the class definitions. I won't bore you with the details, but you can see the
  final results for yourself if you download the code at the end of the
  article.
  This type of tidying up may seem trivial but it's extremely important. If
  your code doesn't look clean, you (and others who find themselves working on
  your code) won't have any incentive to keep it clean. The Pragmatic Programmers
  call this the Broken
  Window Theory. If you live with broken windows, don't be surprised when
  your neighbors start using your lawn as a junk yard.
  I have about 60 non-blank lines of code so far, spread across eight classes.
  That's not too much, but the code is very simple and yet highly flexible. I
  seriously doubt I would have been able to conceive of this design without
  writing my tests first.
  What's even cooler is that I have about 90 non-blank lines of test code.
  Yes, I have more test code than I have "real" code. Is that wrong? Absolutely
  not. That's wonderful! I feel extremely confident about the quality of the code
  that I have so far. Is it perfect? I doubt it. When I discover a bug, though, I
  can add a new test to demonstrate it and fix it so that it never happens again.
  If I need to perform an optimization, I'll have a suite of tests I can use to
  verify that I didn't screw anything up while applying the optimization.
  What's also interesting to note is that the design that emerged from this
  work. I spent zero time in front of a modeling tool trying to create a design
  that would both meet my needs today and still be elegant enough to (hopefully)
  meet all of tomorrow's needs, as well. I didn't intend for this to happen. It
  just magically happened that way. This isn't rare--this almost always happens
  when I do test-driven development.
  How is this design more flexible than I originally intended? Suppose that I
  want to create a pattern that matches every Friday the 13th. That wasn't one
  of my original use cases, and I gave no thought to it while writing the tests.
  The classes I came up have no trouble representing that pattern:
  >>> import DatePatterns
  >>> fri13 = DatePatterns.CompositePattern()
  >>> fri13.add(DatePatterns.WeekdayPattern(DatePatterns.FRIDAY))
  >>> fri13.add(DatePatterns.DayPattern(13))
  >>> import datetime
  >>> aug13 = datetime.date(2004, 8, 13)
  >>> aug13.strftime('%A')
  'Friday'
  >>> fri13.matches(aug13)
  True
  >>> sep13 = datetime.date(2004, 9, 13)
  >>> sep13.strftime('%A')
  'Monday'
  >>> fri13.matches(sep13)
  False
  While I'm not done with the application yet, I do have a solid
  foundation to build on. Next, I need to add some parsing code so
  that I can read a file containing events in order to construct and
  use the patterns I implemented above. I'll visit that task next
  time.
  The code
  and tests are
  available for download and inspection.
  Jason Diamond
  is a consultant specializing in C++, C#, and XML, and is located in sunny Southern California.
  Return to the Python DevCenter.
  Sponsored by:
  © 2014, O’Reilly Media, Inc.
  (707) 827-7019
  (800) 889-8969
  All trademarks and registered trademarks appearing on oreilly.com are the property of their respective owners.