Amin Etesamian Amin Etesamian - 2 months ago 17
Python Question

Testing a time-dependent method

I have a method that takes a datetime and returns what period of time this datetime belongs to, for example "yesterday" or "a month ago".

from datetime import datetime


def tell_time_ago(basetime):
difference = datetime.now() - basetime

days = difference.days
seconds = difference.seconds
hours = seconds / 3600
minutes = seconds / 60

if days and days == 1:
return 'Yesterday'
elif days and days != 1 and days < 7:
return '%s days ago' % days
elif days and days != 1 and 7 < days < 31:
return 'Within this month'
elif days and days != 1 and 30 < days < 365:
return '%s months ago' % (days / 30)
elif days and days != 1 and 365 <= days < 730:
return 'A year ago'
elif days and days != 1 and days >= 730:
return '%s years ago' % (days / 365)

elif hours and hours == 1:
return 'An hour ago'
elif hours and hours != 1:
return '%s hours ago' % hours

elif minutes and minutes == 1:
return 'A minute ago'
elif minutes and minutes != 1:
return '%s minutes ago' % minutes

elif seconds and seconds == 1:
return 'A second ago'
elif seconds and seconds != 1:
return '%s seconds ago' % seconds

else:
return '0 second ago'


I want to extend this method so I'm going to write tests for it. If want to write a test for this method, should I change the current date of system to a specific date and revert it back to normal every time, so the test won't fail just because the date is changed? For example:

class TestCase(unittest.TestCase):
def test_timedelta(self):
a_year_ago = datetime(2015, 5, 12, 23, 15, 15, 53000)
assert tell_time_ago(a_year_ago) == 'A year ago'


If I run this test two years from now, it will fail. What is the best approach?

Answer

In general, I think it's a good idea to make your nontrivial functions and classes as close to mathematical functions (e.g., sin(x)), as possible. Given the same input, a mathematical function gives the same output each time, irrespective of the current date, random choices, and so forth.

  • If your function performs nontrivial logic dependent on the current date or time, pass the current date or time externally to it.

  • If your function performs random choices, pass it a pseudo-random number generator.

So, for example, instead of:

import datetime

def foo():
    ...
    now = datetime.datetime.now()
    ...

foo()

Use

import datetime

def foo(now):
    ...
    ...

foo(datetime.datetime.now())

This makes your nontrivial code consistent across multiple executions.

  1. You can predictably test it.

  2. If it fails in production, it is easier to reconstruct the problem.