How to mock datetime.date.today() method in Django 1.5.5 tests
我有一组测试依赖于使用python模拟库模拟日期,以及
1 2 3 4 | class FakeDate(original_date): "A fake replacement for datetime.date that can be mocked for testing." def __new__(cls, *args, **kwargs): return original_date.__new__(original_date, *args, **kwargs) |
在我们的测试中,我们有:
1 2 3 4 5 6 7 8 9 | from datetime import date as real_date @mock.patch('datetime.date', FakeDate) def test_mondays_since_date(self): FakeDate.today = classmethod(lambda cls: real_date(2014, 1, 1)) # A Wednesday self.assertNotEqual(datetime.date.today(), real_date.today()) self.assertEqual(datetime.date.today().year, 2014) # and so on.. |
一直在努力,直到我将Django从1.4.8升级到1.5.5。不幸的是,现在模拟日期导致测试失败,但仅限于模型保存操作。堆栈跟踪如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | File"/site-packages/django/db/models/base.py", line 546, in save force_update=force_update, update_fields=update_fields) File"/site-packages/django/db/models/base.py", line 650, in save_base result = manager._insert([self], fields=fields, return_id=update_pk, using=using, raw=raw) File"/site-packages/django/db/models/manager.py", line 215, in _insert return insert_query(self.model, objs, fields, **kwargs) File"/site-packages/django/db/models/query.py", line 1675, in insert_query return query.get_compiler(using=using).execute_sql(return_id) File"/site-packages/django/db/models/sql/compiler.py", line 942, in execute_sql for sql, params in self.as_sql(): File"/site-packages/django/db/models/sql/compiler.py", line 900, in as_sql for obj in self.query.objs File"/site-packages/django/db/models/fields/__init__.py", line 304, in get_db_prep_save prepared=False) File"/site-packages/django/db/models/fields/__init__.py", line 738, in get_db_prep_value value = self.get_prep_value(value) File"/site-packages/django/db/models/fields/__init__.py", line 733, in get_prep_value return self.to_python(value) File"/site-packages/django/db/models/fields/__init__.py", line 697, in to_python parsed = parse_date(value) File"/site-packages/django/utils/dateparse.py", line 36, in parse_date match = date_re.match(value) TypeError: expected string or buffer |
我已经进入Django源代码了,问题似乎就在这里(在django / db / models / fields / init.py中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | def to_python(self, value): if value is None: return value if isinstance(value, datetime.datetime): if settings.USE_TZ and timezone.is_aware(value): # Convert aware datetimes to the default time zone # before casting them to dates (#17742). default_timezone = timezone.get_default_timezone() value = timezone.make_naive(value, default_timezone) return value.date() if isinstance(value, datetime.date): # <-- This is the problem! return value try: parsed = parse_date(value) |
类型相等表达式失败,因此调用
所以,我知道问题出在哪里,但我能做些什么来绕过它呢?模拟日期对我们的应用程序测试至关重要。
[编辑1:Django早期版本的比较]
为了比较Django 1.5.5中失败的上述表达式,以下是1.4.8(不会失败):
1 2 3 4 5 6 7 8 9 10 11 12 | def to_python(self, value): if value is None: return value if isinstance(value, datetime.datetime): return value.date() if isinstance(value, datetime.date): return value value = smart_str(value) try: parsed = parse_date(value) |
即他们是一样的。那么为什么一个人过世而另一个人失败 - 这是否与测试员的变化有关?
[编辑2:更多调试]
进一步挖掘出差异:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | > /site-packages/django/db/models/fields/__init__.py(685)to_python() 684 import ipdb; ipdb.set_trace() --> 685 if value is None: 686 return value ipdb> value datetime.date(2012, 12, 7) ipdb> isinstance(value, datetime.date) False ipdb> type(value) <type 'datetime.date'> ipdb> type(datetime.date) <type 'type'> ipdb> datetime.date <class 'testutils.FakeDate'> ipdb> datetime.datetime <type 'datetime.datetime'> |
[编辑3:找到问题]
我发现了1.4和1.5分支之间的差异,而且它不在测试运行中。关键是1.4分支中的
这是1.4.x分支中的序列:
1 2 3 4 5 6 7 8 | # value = FakeDate(2012, 12, 31) # code fails the isinstance(datetime.date) test value = smart_str(value) # value is now a string '2012-12-31' parsed = parse_date(value) # inside parse_date we try a regex match match = date_re.match(value) # because we have called smart_str, this now parses as a date |
1.5.x分支中的序列不包括smart_str转换,因此正则表达式匹配失败,因为此情况下的
[编辑5:提交给Django的Bug]
我已经向Django问题跟踪器(https://code.djangoproject.com/ticket/21523)提交了一个错误。
我对此的调查是作为一组编辑的问题,但是长期和短期是对to_python方法的更改在1.4和1.5之间意味着任何既不是有效的datetime.date也不是datetime.datetime的东西必须是字符串以便通过。
它看起来(没有太多的进一步挖掘)好像在1.5中删除了
我在django Trac实例中提出了一张票,https://code.djangoproject.com/ticket/21523
我也为这个问题创建了一个补丁 - 但显然这可能永远不会进入(并且补丁适用于1.5.x,已经过时了,所以我真的不会指望这个让它进入) 。
[编辑1:解决方案!]
有一个解决方案;-) - 我在这里做了一个写 - http://tech.yunojuno.com/mocking-dates-with-django - 关键是覆盖FakeDate instancecheck方法,以便在比较实际日期时间.date到FakeDate你得到真。我已经将一些示例FakeDate类和相关测试放在一起,以供参考 - https://gist.github.com/hugorodgerbrown/7750432