使用基于日期/时间的对象进行Django单元测试

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)

希望这可以帮助


Ste*_*eef 7

您可以编写自己的日期时间模块替换类,从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 .


Joh*_*ery 6

Steef的解决方案略有不同.而不是全局替换日期时间,而只需在您正在测试的模块中替换datetime模块,例如:


import models # your module with the Event model
import datetimestub

models.datetime = datetimestub.DatetimeStub()

这样,在测试期间,更改会更加本地化.

  • 这将涉及更改您正在测试的代码,虽然不是吗?您真正想要做的就是在模块模块中重新绑定名称"datetime". (2认同)