views:

138

answers:

4

When I write with business logic, my code often depends on the current time. For example the algorithm which looks at each unfinished order and checks if an invoice should be sent (which depends on the no of days since the job was ended). In these cases creating an invoice is not triggered by an explicit user action but by a background job.

Now this creates a problem for me when it comes to testing:

  • I can test invoice creation itself easily
  • However it is hard to create an order in a test and check that the background job identifies the correct orders at the correct time.

So far I found two solutions:

  • In the test setup, calculate the job dates relative to the current date. Downside: The code becomes quite complicated as there are no explicit dates written anymore. Sometimes the business logic is pretty complex for edge cases so it becomes hard to debug due to all these relative dates.
  • I have my own date/time accessor functions which I use throughout my code. In the test I just set a current date and all modules get this date. So I can simulate an order creation in February and check that the invoice is created in April easily. Downside: 3rd party modules do not use this mechanism so it's really hard to integrate+test these.

The second approach was way more successful to me after all. Therefore I'm looking for a way to set the time Python's datetime+time modules return. Setting the date is usually enough, I don't need to set the current hour or second (even though this would be nice).

Is there such a utility? Is there an (internal) Python API that I can use?

A: 

there might be few ways of doing this, like creating the orders (with the current timestamp) and then changing that value in the DB directly by some external process (assuming data is in the DB).

I'll suggest something else. Have you though about running your application in a virtual machine, setting the time to say Feb, creating orders, and then just changing the VMs time? This approach is the closest as you can get to the real-life situation.

pulegium
Yes, moving dates in the DB directly could work. However that's sometimes not easily doable - especially you have a lot of date fields in various tables (e.g. every item has timestamps for created, changed + the complete history). So changing all of these might be a major hassle.For the VMs: I won't go there as this will increase testing complexity by some orders of magnitude -> test flakiness is way more likely, tests run slower (fast tests are extremly important to me!) and it requires some major effort to integrate that on my build servers.
Felix Schwarz
A: 

Well one way to do it is to dynamic patch the time /datetime module

something like

import time
import datetime

class MyDatetime:

    def now(self):
        return time.time()

datetime.datetime = MyDatetime

print datetime.datetime().now()
fabrizioM
Yes, that could work - however it is still too fragile because I need to monkey-patch all external modules: If an external module uses 'from datetime import datetime' the monkey patch above won't have any effect.
Felix Schwarz
@Felix, it will have an effect if you patch the datetime module prior to importing anything else. If you're planning to do monkey-patching (which is not an elegant solution, but may be pragmatic) then you have to live with such awkwardness.
Peter Hansen
A: 

You can patch the system, by creating a custom datetime module (even a fake one - see example below) acting as a proxy and then insert it in sys.modules dictionary. From there on, each import to the datetime module will return your proxy.
There is still the caveat of datetime class, especially when someone does from datetime import datetime; for that, you can simply add another proxy only for that class.

Here is an example of what I am saying - of course it is just something I've thrown in 5 minutes, and may have several issues (for instance, the type of datetime class is not correct); but hopefully it may already be of use.

import sys
import datetime as datetime_orig

class DummyDateTimeModule(sys.__class__):
  """ Dummy class, for faking datetime module """
  def __init__(self):
    sys.modules["datetime"] = self
  def __getattr__(self, attr):
    if attr=="datetime":
      return DummyDateTimeClass()
    else:
      return getattr(datetime_orig, attr)

class DummyDateTimeClass(object):
  def __getattr__(self, attr):
    return getattr(datetime_orig.datetime, attr)

dt_fake = DummyDateTimeModule()

Finally - is it worth?
Frankly speaking, I like our second solution much more than this one :-).
Yes, python is a very dynamic language, where you can do quite a lot of interesting things, but patching code in this way has always a certain degree of risk, even if we are talking here of test code.
But mostly, I think the accessory function would make test patching more explicit, and also your code would be more explicit in terms of what it is going to be tested, thus increasing readability.
Therefore, if the change is not too expensive, I would go for your second approach.

Roberto Liffredo
+1  A: 

Monkey-patching time.time is probably sufficient, actually, as it provides the basis for almost all the other time-based routines in Python. This appears to handle your use case pretty well, without resorting to more complex tricks, and it doesn't matter when you do it (aside from the few stdlib packages like Queue.py and threading.py that do from time import time in which case you must patch before they get imported):

>>> import datetime
>>> datetime.datetime.now()
datetime.datetime(2010, 4, 17, 14, 5, 35, 642000)
>>> import time
>>> def mytime(): return 120000000.0
...
>>> time.time = mytime
>>> datetime.datetime.now()
datetime.datetime(1973, 10, 20, 17, 20)

That said, in years of mocking objects for various types of automated testing, I've needed this approach only very rarely, as most of the time it's my own application code that needs the mocking, and not the stdlib routines. After all, you know they work already. If you are encountering situations where your own code has to handle values returned by library routines, you may want to mock the library routines themselves, at least when checking how your own app will handle the timestamps.

The best approach by far is to build your own date/time service routine(s) which you use exclusively in your application code, and build into that the ability for tests to supply fake results as required. For example, I do a more complex equivalent of this sometimes:

# in file apptime.py (for example)
import time as _time

class MyTimeService(object):
    def __init__(self, get_time=None):
        self.get_time = get_time or _time.time

    def __call__(self):
        return self.get_time()

time = MyTimeService()

Now in my app code I just do import apptime as time; time.time() to get the current time value, whereas in test code I can first do apptime.time = MyTimeService(mock_time_func) in my setUp() code to supply fake time results.

Peter Hansen