The Artima Developer Community
Sponsored Link

Weblogs Forum
Stupid datetime testing

4 replies on 1 page. Most recent reply: Apr 20, 2009 12:51 AM by Patrick Kua

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 4 replies on 1 page
Barry Warsaw

Posts: 12
Nickname: pumpichank
Registered: Jun, 2003

Stupid datetime testing (View in Weblogs)
Posted: Mar 6, 2009 6:41 PM
Reply to this message Reply
Summary
Testing APIs that use the current date and time are a pain because those values are variable. In Mailman 3 I hit upon a really dumb, simple way to do this that doesn't suck.
Advertisement

So here's the problem: Let's say you have a database class that is unit tested. I like doctests, but maybe you like Python unittest. Your class has a column (exposed in your ORM class) for the date that the row was last touched, which is initialized from today's date. How do you test this?

Your test could just ensure that some date was stuffed in the attribute/column, but you can't really tell if it's the date you care about because that's going to be different every day you run the test. So this solution isn't very good.

You could change your API to allow you to pass a known date into it, and your test could call this extended API, but that's not great for several reasons. You'd rather not clutter your API up with extra parameters only used in testing, and it means your ORM class now has different logic for testing environments and production environments. So I don't like this much either.

I'm sure you've thought of your own approaches, including hacking Python's standard datetime module to stuff in instances that allow you to override datetime.datetime.now() and datetime.date.today(). I thought of that too! Because these are built-in types, you can't just replace those methods, but Python does make it fairly easy to subclass built-in types. So that would seem like a good approach except...

I use the Storm ORM in Mailman 3 and it has very strict type checking on its column input values. Generally this is a good thing, but in this case it prevents the subclassing approach because the DateTime column type won't accept subclasses of the built-in datetime type.

The approach I settled on seems the least sucky to me. It's also stupid simple, so I kind of like it for that reason too! I wrote a simple wrapper class that only returns now() and today() and I make sure all the testable call sites call these methods instead of the datetime module's versions. The now() and today() in my utility module then are really instance methods of a class which can be told whether it's in testing mode or not. When it's not in testing mode, it simply returns datetime.datetime.now() and datetime.date.today() as usual. When it is in testing mode, it returns instead a known date and time, which of course, you can test!

The class itself has two additional class methods. One resets the current date and time (in testing mode) to the original known values. The other allows you to fast forward the known date and time. Here's the (stripped down) code. Note that because of the conditional expressions, Python 2.5 is required:

from __future__ import absolute_import, unicode_literals

__metaclass__ = type

import datetime

class DateFactory:
    """A factory for today() and now() that works with testing."""

    # Set to True to produce predictable dates and times.
    testing_mode = False
    # The predictable time.
    predictable_now = None
    predictable_today = None

    def now(self, tz=None):
        return (self.predictable_now
                if self.testing_mode
                else datetime.datetime.now(tz))

    def today(self):
        return (self.predictable_today
                if self.testing_mode
                else datetime.date.today())

    @classmethod
    def reset(cls):
        cls.predictable_now = datetime.datetime(2005, 8, 1, 7, 49, 23)
        cls.predictable_today = cls.predictable_now.date()

    @classmethod
    def fast_forward(cls, days=1):
        cls.predictable_now += datetime.timedelta(days=days)
        cls.predictable_today = cls.predictable_now.date()

factory = DateFactory()
factory.reset()
today = factory.today
now = factory.now

Now, at the call sites, say in your database class, instead of something like:

import datetime
self.date_created = datetime.date.today()

You'd write:

from ... import today
self.date_created = today()

And your test would do something like this:

>>> from ... import factory
>>> factory.testing_mode = True

>>> thing1 = Thing()
>>> thing1.date_created
datetime.datetime(2005, 8, 1, 7, 49, 23)

>>> factory.fast_forward(days=3)

>>> thing2 = Thing()
>>> thing2.date_created
datetime.datetime(2005, 8, 4, 7, 49, 23)

I know there are mocking libraries out there that might help you with this, but I also think this is a nice simple approach where you don't want a lot of extra mocking scaffolding. The cost is that you have to be disciplined not to use the standard datetime module at your call sites.


Michele Simionato

Posts: 222
Nickname: micheles
Registered: Jun, 2008

Re: Stupid datetime testing Posted: Mar 6, 2009 9:39 PM
Reply to this message Reply
> Note that because of the conditional expressions, Python 2.6 is required

Typo: I am sure you meant Python 2.5 here, right?

Another possibility would be to monkey patch the datetime
module:

if testing_mode:
import datetime
datetime.datetime=myfakedatetimeclass

to be done at the beginning of the tests.

Barry Warsaw

Posts: 12
Nickname: pumpichank
Registered: Jun, 2003

Re: Stupid datetime testing Posted: Mar 7, 2009 7:21 AM
Reply to this message Reply
Thanks for the correction! Yes, 2.5 is required; I've fixed the original post.

Patching the datetime class doesn't work in my case because of the Storm limitation. It requires a concrete datetime.datetime instance.

Rupert Kittinger-Sereinig

Posts: 21
Nickname: rkit
Registered: Dec, 2005

Re: Stupid datetime testing Posted: Mar 28, 2009 1:31 AM
Reply to this message Reply
As a completely different appoach, how about setting the system time to a well-defined value during the tests?

This is also useful for other test cases, like leap years or leap seconds.

Patrick Kua

Posts: 2
Nickname: thekua
Registered: Apr, 2009

Re: Stupid datetime testing Posted: Apr 20, 2009 12:51 AM
Reply to this message Reply
I've used very similar concepts in the past, but inject a clock instead of a DateFactory (using different "clocks" to achieve different results). I'd written something quite recent about how to implement this in java (link below).

I would recommend separating the responsibility for fetching current time/date/days from being able to manipulate it. I tend to think the manipulation is really a testing aspect only.

Cheers!


Patrick

http://www.thekua.com/atwork/2009/01/controlling-time/

Flat View: This topic has 4 replies on 1 page
Topic: Seven Steps to Disaster Previous Topic   Next Topic Topic: What I learned at Java Posse Roundup '09

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use