views:

260

answers:

2

I've written an auction system in Django. I want to write unit tests but the application is time sensitive (e.g. the amount advertisers are charged is a function of how long their ad has been active on a website). What's a good approach for testing this type of application?

Here's one possible solution: a DateFactory class which provides some methods to generate a predictable date in testing and the realtime value in production. Do you have any thoughts on this approach, or have you tried something else in practice?

+3  A: 

In the link you provided, the author somewhat rejects the idea of adding additional parameters to your methods for the sake of unit testing, but in some cases I think you can justify this as just an extension of your business logic. In my opinion, it's a form of inversion of control that can make your model more flexible and possibly even more expressive. For example:

def is_expired(self, check_date=None):
    _check_date = check_date or datetime.utcnow()
    return self.create_date + timedelta(days=15) < _check_date

Essentially this allows my unit test to supply its own date/time for the purpose of validating my logic.

The argument in the referenced blog seems to be that this mucks up the API. However, I have encountered situations in which production use cases called for supplanting current date/time with an alternate value. In other words, the inversion of control approach eventually became a necessary part of my application.

Joe Holloway
+1  A: 

In general I try to make the production code take date objects as input (where the semantics allows). In many testing situations a DateFactory as you describe is what people do.

In Python you can also get away with changing the static module methods Datetime.now or Time.now directly. You need to be careful here to replace them in the teardown part of the test. This is particularly useful when you are unable to (or it is awkward to) change the class you are testing.

To do this you have

   def setUp(self) 
      self.oldNow = Datetime.now
      Datetime.now = self._fakenow
      ...

   def tearDown(self)
      Datetime.now = self.oldNow

I do the substitutions last if there is the slightest possiblity that the setup method will fail.

For many cases a custom DateFactory is safer to use, particularly if you have to worry about people forgetting the tearDown portion.

Kathy Van Stone