用于测试目的的与日期和时间相关的各种实用工具。
项目描述
此包允许在测试中“欺骗”当前时间。它最初在Odoo中使用,用于测试跨越长时间的工作流程。
目前它主要提供了一个datetime.set_now()方法来模拟当前时间,以及一个datetime.real_now()来返回到原始时间。
用法
在开始之前,必须导入包以用修改后的版本替换常规的datetime模块
>>> import anybox.testing.datetime >>> from datetime import datetime >>> import time
让我们保留now的实际值
>>> start = datetime.now() >>> start_t = time.time() >>> start_s = time.strftime('%Y-%m-%d') >>> start_l = time.localtime() >>> start_c = time.ctime()
然后您可以更改当前时间
>>> datetime.set_now(datetime(2001, 01, 01, 3, 57, 0)) >>> datetime.now() datetime(2001, 1, 1, 3, 57) >>> datetime.today() datetime(2001, 1, 1, 3, 57)
时间模块继续使用
>>> datetime.fromtimestamp(time.time()) datetime(2001, 1, 1, 3, 57)
请注意,您可能会期望一些微秒级差异(在此未显示,因为datetime.fromtimestamp忽略了它们)。
时间模块中的一些其他函数也会返回当前时间
>>> time.localtime() time.struct_time(tm_year=2001, tm_mon=1, tm_mday=1, tm_hour=3, tm_min=57, tm_sec=0, tm_wday=0, tm_yday=1, tm_isdst=-1) >>> time.strftime('%Y-%m-%d') '2001-01-01' >>> time.ctime() 'Mon Jan 1 03:57:00 2001' >>> time.asctime() 'Mon Jan 1 03:57:00 2001' >>> time.gmtime() time.struct_time(tm_year=2001, tm_mon=1, tm_mday=1, tm_hour=3, tm_min=57, tm_sec=0, tm_wday=0, tm_yday=1, tm_isdst=-1)
其余行为未改变
>>> time.localtime(0).tm_year 1970 >>> time.strftime('%Y-%m-%d', datetime(1999,9,9).timetuple()) '1999-09-09' >>> time.ctime(5) 'Thu Jan 1 02:00:05 1970' >>> time.asctime(time.localtime(5)) 'Thu Jan 1 02:00:05 1970' >>> time.gmtime(5.0) time.struct_time(tm_year=1970, tm_mon=1, tm_mday=1, tm_hour=0, tm_min=0, tm_sec=5, tm_wday=3, tm_yday=1, tm_isdst=0)
之后不要忘记回到常规系统时钟,否则许多代码片段可能会因为系统时钟看起来像被冻结而感到非常惊讶
>>> datetime.real_now()
现在让我们检查它是否工作
>>> now = datetime.now() >>> now > start True >>> from datetime import timedelta >>> now - start < timedelta(0, 0, 10000) # 10 ms True
以及使用时间模块
>>> now_t = time.time() >>> now_t > start_t True >>> now_t - start_t < 0.01 # 10 ms again True >>> time.strftime('%Y-%m-%d') == start_s True >>> time.localtime().tm_mday == start_l.tm_mday True
其他构造函数仍然可用(这是一个非回归测试)
>>> import datetime >>> datetime.time(3, 57, 0) datetime.time(3, 57) >>> datetime.datetime(2013, 1, 1, 3, 57, 0) datetime(2013, 1, 1, 3, 57) >>> datetime.date(2013, 1, 1) datetime.date(2013, 1, 1)
幕后
我们的替代类是从 datetime 模块加载的,但原始 datetime 类的实例行为与我们的 datetime.datetime 类实例完全相同。这是因为大多数计算方法实际上返回原始 datetime 类的对象。这仅在 python >= 2.6 时才有效。
首先,让我们检查我们的类是否是原始类的子类。如果这失败了,这个测试就不再有任何意义了。
>>> datetime.datetime is datetime.original_datetime False >>> issubclass(datetime.datetime, datetime.original_datetime) True
然后,让我们展示一下这种行为。
>>> odt = datetime.original_datetime(2012, 1, 1) >>> isinstance(odt, datetime.datetime) True >>> issubclass(datetime.original_datetime, datetime.datetime) True
从现在开始,我们需要一个 tzinfo 子类。
>>> from datetime import tzinfo >>> class mytzinfo(tzinfo): ... def utcoffset(self, dt): ... return timedelta(hours=2) ... def dst(self, dt): ... return timedelta(0)
兼容性
在开发工具包模块的生命周期中,我们不得不确保与几个子系统的兼容性。
日志记录
在日志记录模块中,使用 time.localtime 作为方法。我们只需检查它是否工作即可。
>>> import logging >>> datetime.datetime.set_now(datetime.datetime(2000, 1, 1)) >>> logging.Formatter().converter().tm_year >= 2014 True >>> datetime.datetime.real_now()
SQLite
此外,sqlite3 也识别我们的 datetime 和 date 类,就像它们是原始类一样。
>>> import sqlite3 >>> cnx = sqlite3.connect(':memory:') >>> cr = cnx.cursor() >>> cr = cr.execute("CREATE TABLE dates (dt text, d text)") >>> dt = datetime.datetime(2013, 1, 25, 12, 34, 0) >>> d = datetime.date(2013, 4, 7) >>> cr = cr.execute("INSERT INTO dates VALUES (?, ?)", (dt, d)) >>> cr = cr.execute("SELECT dt, d from dates") >>> cr.fetchall() [(u'2013-01-25 12:34:00', u'2013-04-07')]
恢复原始时间
现在让我们再次尝试使用原始类。
>>> dt = datetime.datetime.now() >>> isinstance(dt, datetime.original_datetime) True >>> d = datetime.date.today() >>> cr = cr.execute("INSERT INTO dates VALUES (?, ?)", (dt, d)) >>> cr = cr.execute("SELECT dt, d from dates") >>> res = cr.fetchall() # can't check the value, it changes a lot !
数据流即 pickling
mock_dt 支持 pickling。
>>> import pickle >>> from StringIO import StringIO >>> stream = StringIO() >>> v = datetime.datetime(2013, 1, 1, 3, 57, 0) >>> pickle.dump(v, stream) >>> stream.seek(0) >>> v2 = pickle.load(stream) >>> v == v2 True >>> type(v2) <class 'anybox.testing.datetime.mock_dt.datetime'> >>> stream = StringIO() >>> v = datetime.datetime.now() >>> pickle.dump(v, stream) >>> stream.seek(0) >>> v2 = pickle.load(stream) >>> v == v2 True >>> type(v2) <class 'anybox.testing.datetime.mock_dt.datetime'> >>> stream = StringIO() >>> datetime.datetime.set_now(datetime.datetime(2001, 01, 01, 3, 57, 0)) >>> v = datetime.datetime.now() >>> pickle.dump(v, stream) >>> stream.seek(0) >>> v2 = pickle.load(stream) >>> v == v2 True >>> type(v2) <class 'anybox.testing.datetime.mock_dt.datetime'>
测试
这个 README 也是一个 doctest。要测试它和其他这个包的 doctests,只需安装 Nose 并运行。
$ nosetests
变更
0.5 (2015-04-12)
添加了对 time.* 函数的支持
#6: 修复了 pickling 错误
0.4.2 (2013-06-11)
文档中的一些改进
0.4.1 (2013-04-24)
#3: 修复了与 sqlite3 的兼容性问题(通过 IPython/IPdb 发现)
0.3.1 (2012-11-28)
#1: 测试代码现在使用 now() 的时区可选参数不会导致任何破坏(尽管没有真正的时区支持)
0.3 (2012-11-23)
修复了由计算生成的 datetime 对象在 isinstance 测试中失败的問題。
0.2.1 (2012-11-22)
修复了 datetime.time 遮蔽问题
0.1 (2012-07-15)
初始版本