Time intervals and testing

How to unit-test python modules that need real date/timestamps

In many different projects, I have found the need to work with time intervals, especially in testing contexts. The following examples come to my mind:

  • I need to test that some code runs in less than a certain amount of time (typically to test for regression of code that might freeze for sometime or has a timeout)
  • I need to test that after running some code, a value (an attribute with a timestamp, for example), was set to a “current” value.
  • I need to test that some time value was not producing during the test

Some of these needs can be covered by mocking, but mocking very low-level, widely used modules as datetime can have undesired effects or be difficult: some code might need to call the mocked functions many times and depend on getting different values, or use a library that also used the mocked module. Other times, you don’t want to specify in the test which function to mock, because it ties the tested code to a specific implementation; perhaps the tested code gets its time values from a file, a database, or some other computation rather than the datetime module in python.

For those cases when I want the test, but I can’t/don’t want to mock, I have used this small snippet as a utility class:

import contextlib
from datetime import datetime


class Timer(object):

    def start(self):
        self.start = datetime.now()

    def stop(self):
        self.stop = datetime.now()

    def elapsed(self):
        return self.stop - self.start

    def assertHasDate(self, date):
        assert self.start <= date <= self.stop, \
               "%s was not during the timer" % date

    def assertNotHasDate(self, date):
        assert not (self.start <= date <= self.stop), \
               "%s was during the timer" % date

This simple timer allows you to make quick time measurements as a standalone class by itself, for example:

t = Timer()
t.start()
# Some code that takes some time
t.stop()
print("elapsed: %s" % t.elapsed())

But it also allows you to make some checks to relate a datetime to the interval when the timer was running:

def test_something(self):
    t = Timer()
    t.start()
    obj = create_some_object(...)
    t.stop()
    t.assertHasDate(obj.timestamp)

I’ve also found useful to add the following snippet to my timer module:

import contextlib

@contextlib.contextmanager
def timekeeper():
    t = Timer()
    t.start()
    try:
        yield t
    finally:
        t.stop()

After that, it’s possible to use this as a context manager in tests, which makes the pattern very simple:

def test_something(self):
    with timekeeper() as t:
        obj = create_some_object(...)
    t.assertHasDate(obj.timestamp)

You can find the complete code for this post at: <https://gist.github.com/dmoisset/9475548>


Previous / Next posts


Comments