如何在Python中更改所有模块的日期/时间?

Fel*_*arz 20 python testing time datetime unit-testing

当我使用业务逻辑编写时,我的代码通常取决于当前时间.例如,查看每个未完成订单并检查是否应发送发票的算法(这取决于作业结束后的天数).在这些情况下,创建发票不是由显式用户操作触发,而是由后台作业触发.

现在,在测试方面,这给我带来了一个问题:

  • 我可以轻松地测试发票创建本身
  • 但是,很难在测试中创建订单并检查后台作业是否在正确的时间识别正确的订单.

到目前为止,我找到了两个解

  • 在测试设置中,计算相对于当前日期的作业日期.缺点:代码变得非常复杂,因为没有明确的日期.有时,业务逻辑对于边缘情况非常复杂,因此由于所有这些相对日期而变得难以调试.
  • 我有自己的日期/时间访问器功能,我在整个代码中都使用它.在测试中我只设置了一个当前日期,所有模块都得到了这个日期.因此,我可以在二月份模拟订单创建,并检查发票是否在四月轻松创建.缺点:第三方模块不使用此机制,因此很难集成+测试这些.

毕竟,第二种方法对我来说更成功.因此,我正在寻找一种方法来设置Python的datetime + time模块返回的时间.设置日期通常就足够了,我不需要设置当前小时或秒(即使这样会很好).

有没有这样的实用工具?是否有可以使用的(内部)Python API?

Pet*_*sen 9

time.time实际上,Monkey-patching 可能就足够了,因为它为Python中几乎所有其他基于时间的例程提供了基础.这似乎可以很好地处理你的用例,而不需要采用更复杂的技巧,并且当你这样做时并不重要(除了像Queue.py和threading.py这样的少数stdlib包之外from time import time你必须修补在他们进口之前):

>>> 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)
Run Code Online (Sandbox Code Playgroud)

也就是说,在各种类型的自动化测试的模拟对象中,我很少需要这种方法,因为大多数时候它是我自己的应用程序代码需要模拟,而不是stdlib例程.毕竟,你知道他们已经工作了.如果您遇到自己的代码必须处理库例程返回的值的情况,您可能希望自己模拟库例程,至少在检查您自己的应用程序如何处理时间戳时.

到目前为止,最好的方法是构建您自己的日期/时间服务例程,您可以在应用程序代码中专门使用这些例程,并构建测试根据需要提供虚假结果的能力.例如,我有时会做一个更复杂的等价物:

# 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()
Run Code Online (Sandbox Code Playgroud)

现在在我的应用程序代码中我只是import apptime as time; time.time()为了获取当前时间值,而在测试代码中我可以先apptime.time = MyTimeService(mock_time_func)在我的setUp()代码中提供假时间结果.

更新:多年后有一个替代方案,如Dave Forgac的答案所述.

  • 您测试的是哪个版本的Python试试这个?我刚刚在MacOS 10.6上尝试过2.6并且datetime.datetime.now()幸福地没有意识到时间的变化. (7认同)
  • 我在使用Python 2.7.3的Ubuntu 12.04上测试了它,但它不起作用.在猴子用`mytime`修补`time.time`之后,调用`datetime.datetime.now`仍然返回当前时间. (4认同)

Dav*_*gac 7

freezegun包是专为此目的作出.它允许您更改测试代码的日期.它可以直接使用,也可以通过装饰器或上下文管理器使用.一个例子:

from freezegun import freeze_time
import datetime

@freeze_time("2012-01-14")
def test():
    assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)
Run Code Online (Sandbox Code Playgroud)

有关更多示例,请参阅该项目:https://github.com/spulec/freezegun

  • 这似乎是最好的选择!不幸的是,该项目仅创建了几年太晚了但我将在未来使用它:-) (2认同)