Fra*_*rth 26 python django datetime unit-testing stub
假设我有以下Event型号:
from django.db import models
import datetime
class Event(models.Model):
date_start = models.DateField()
date_end = models.DateField()
def is_over(self):
return datetime.date.today() > self.date_end
Run Code Online (Sandbox Code Playgroud)
我想Event.is_over()通过创建一个将来结束的事件(今天+ 1或其他)进行测试,并将日期和时间存根,以便系统认为我们已经达到了未来的日期.
就python而言,我希望能够存储所有系统时间对象.这包括datetime.date.today(),datetime.datetime.now()和任何其他标准日期/时间对象.
这样做的标准方法是什么?
Rem*_*ndt 32
编辑:由于我的回答是这里接受的答案,我正在更新它,让大家知道在此期间创建了一个更好的方法,冻结库:https://pypi.python.org/pypi/freezegun .当我想在测试中影响时间时,我在所有项目中使用它.看看它.
原始答案:
更换这样的内部东西总是很危险,因为它可能有令人讨厌的副作用.所以你真正想要的是让猴子修补尽可能地本地化.
我们使用Michael Foord的优秀模拟库:http://www.voidspace.org.uk/python/mock/ ,它有一个@patch修饰器可以修补某些功能,但猴子补丁只存在于测试功能的范围内,一切都是函数超出其范围后自动恢复.
唯一的问题是内部datetime模块是用C实现的,所以默认情况下你不能修补它.我们通过制作可以模拟的简单实现来解决这个问题.
总解决方案是这样的(示例是Django项目中用于验证日期是否在未来的验证器函数).请注意,我从一个项目中获取了这个,但是拿出了非重要的东西,所以当复制粘贴时,事情可能实际上不起作用,但是你明白了,我希望:)
首先,我们datetime.date.today在一个名为的文件中定义我们自己非常简单的实现utils/date.py:
import datetime
def today():
return datetime.date.today()
Run Code Online (Sandbox Code Playgroud)
然后我们在这个验证器中创建unittest tests.py:
import datetime
import mock
from unittest2 import TestCase
from django.core.exceptions import ValidationError
from .. import validators
class ValidationTests(TestCase):
@mock.patch('utils.date.today')
def test_validate_future_date(self, today_mock):
# Pin python's today to returning the same date
# always so we can actually keep on unit testing in the future :)
today_mock.return_value = datetime.date(2010, 1, 1)
# A future date should work
validators.validate_future_date(datetime.date(2010, 1, 2))
# The mocked today's date should fail
with self.assertRaises(ValidationError) as e:
validators.validate_future_date(datetime.date(2010, 1, 1))
self.assertEquals([u'Date should be in the future.'], e.exception.messages)
# Date in the past should also fail
with self.assertRaises(ValidationError) as e:
validators.validate_future_date(datetime.date(2009, 12, 31))
self.assertEquals([u'Date should be in the future.'], e.exception.messages)
Run Code Online (Sandbox Code Playgroud)
最终实现如下:
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from utils import date
def validate_future_date(value):
if value <= date.today():
raise ValidationError(_('Date should be in the future.'))
Run Code Online (Sandbox Code Playgroud)
希望这可以帮助
您可以编写自己的日期时间模块替换类,从datetime实现要替换的方法和类.例如:
import datetime as datetime_orig
class DatetimeStub(object):
"""A datetimestub object to replace methods and classes from
the datetime module.
Usage:
import sys
sys.modules['datetime'] = DatetimeStub()
"""
class datetime(datetime_orig.datetime):
@classmethod
def now(cls):
"""Override the datetime.now() method to return a
datetime one year in the future
"""
result = datetime_orig.datetime.now()
return result.replace(year=result.year + 1)
def __getattr__(self, attr):
"""Get the default implementation for the classes and methods
from datetime that are not replaced
"""
return getattr(datetime_orig, attr)
Run Code Online (Sandbox Code Playgroud)
让我们把它放在我们称之为的模块中 datetimestub.py
然后,在测试开始时,您可以这样做:
import sys
import datetimestub
sys.modules['datetime'] = datetimestub.DatetimeStub()
Run Code Online (Sandbox Code Playgroud)
然后,datetime模块的任何后续导入都将使用该datetimestub.DatetimeStub实例,因为当模块的名称用作sys.modules字典中的键时,将不会导入模块:sys.modules[module_name]将使用对象at .
Steef的解决方案略有不同.而不是全局替换日期时间,而只需在您正在测试的模块中替换datetime模块,例如:
import models # your module with the Event model
import datetimestub
models.datetime = datetimestub.DatetimeStub()
这样,在测试期间,更改会更加本地化.