如何使用模拟测试 next_day_of_week 函数

9 投票
2 回答
2573 浏览
提问于 2025-04-16 20:49

我正在跟踪一些在特定星期几重复发生的事件(比如每个月的第一个星期天、每个月的第三个星期五)。我有一个叫做 DayOfWeek 的模型,它用来存储事件的星期几。这个模型里有一个方法叫 next_day_of_week,可以返回一个日期对象,表示某个事件下次发生的日期(这有助于我们确定事件的下次发生时间)。

举个例子,在2011年7月3日的星期天:

  • 如果 DayOfWeek 设置为星期天,next_day_of_week 就会返回 2011年7月3日。
  • 如果设置为星期一,它会返回 2011年7月4日。
  • 如果设置为星期六,它会返回 2011年7月9日。

依此类推。我正在写单元测试(这是我第一次写;我是不是提到过我对这些东西还很陌生?),并试图理解如何测试这个方法。我知道我需要模拟一些东西,但不太确定该模拟什么。这个问题似乎正好涉及我想问的内容:Python: Trying to mock datetime.date.today() but not working

所以我在 tests.py 中尝试模拟 datetime.date:

class FakeDate(date):
"A fake replacement for date that can be mocked for testing."
    def __new__(cls, *args, **kwargs):
        return date.__new__(date, *args, **kwargs)

然后我创建了我的测试用例,使用模拟类并将今天的日期设置为 2011年7月3日:

class TestDayOfWeek(TestCase):
    """Test the day of the week functions."""

    @mock.patch('datetime.date', FakeDate)
    def test_valid_my_next_day_of_week_sameday(self):
        from datetime import date
        FakeDate.today = classmethod(lambda cls: date(2011, 7, 3)) # July 3, 2011 is a Sunday
        new_day_of_week = DayOfWeek.objects.create()
        new_day_of_week.day = "SU"
    self.assertEquals(new_day_of_week.my_next_day_of_week(), date(2011, 7, 3))

作为参考,这里是模型类:

class DayOfWeek(ModelBase):
"""
Represents a day of the week (on which an event can take place). 
Because the dates of these events are often designated by terms like 'first Monday'
or 'third Friday', this field is useful in determining on which dates individual
readings take place.
"""

# The pk in the db is 1-indexed (Monday=1, Tuesday=2, etc), but python's days 
# of the week are 0-indexed if you use .weekday(), so we are using .isoweekday()
# instead. This list is used in my_next_day_of_week.
days =[ 'No day', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',
       'Sunday' ]

DAYS_OF_WEEK_CHOICES = (
('MO', days[1]),
('TU', days[2]),
('WE', days[3]),
('TH', days[4]),
('FR', days[5]),
('SA', days[6]),
('SU', days[7]),
)

day = models.CharField(max_length=2, choices=DAYS_OF_WEEK_CHOICES)

def __unicode__(self):
    for daypair in self.DAYS_OF_WEEK_CHOICES:
        if self.day in daypair:
            return daypair[1]

    # This shouldn't happen
    raise InvalidDayOfWeekError

# my_next_day_of_week returns a datetime equal to the start (midnight+min) of the next day that is this instance's day of the week.
# It doesn't know what time the event is, so if today is the day of the week the event falls on,
# it simply returns today.
def my_next_day_of_week(self):
    """ 
    Returns a datetime equal to the start of the next day that is this instance's day of the week. 
    """

    today_day = date.today().isoweekday() # Find the number of the current day of the week
    reading_day = self.days.index(self.__unicode__()) # Find the number of the instance's day of the week
            # There is probably a more pythonic way to do this next part
    next_day = date.today() # start with next day = today
    while next_day.isoweekday() != reading_day:
        next_day = next_day + timedelta(1)

    return next_day

当我运行 Django 的测试工具时,测试失败了,因为我的 DayOfWeek 实例似乎没有使用模拟的 datetime.date,而是看到了今天的实际日期。从我的理解来看,模拟只在测试方法内部存在,而不在之前或之后。但这是否也意味着它对从测试方法内部实例化或调用的任何对象/方法都不存在?那么它的用途是什么?我觉得这不是问题的所在,而是我在模拟时做错了什么。可能是命名空间的问题?我正在阅读这个:http://www.voidspace.org.uk/python/mock/patch.html#id2。我会继续尝试修复这个问题,如果成功了会更新这个内容,但在此之前,任何建议都非常感谢!

编辑:我意识到我在模型中使用的是 datetime.datetime,而不是 datetime.date。我修正了这个问题并更新了上面的代码,但模拟类没有被使用的问题依然存在。

2 个回答

0

我觉得你需要的答案在另一个帖子里。我已经把它改写成更适合你的需求了。

import datetime
class FakeDate(datetime.date):
    @classmethod
    def today(cls):
        return cls(2011, 7, 3)
datetime.date = FakeDate

class TestDayOfWeek(TestCase):
    """Test the day of the week functions."""

    def test_valid_my_next_day_of_week_sameday(self):
        new_day_of_week = DayOfWeek.objects.create()
        new_day_of_week.day = "SU"
        self.assertEquals(new_day_of_week.my_next_day_of_week(), date(2011, 7, 3))
10

我搞明白了。

问题其实是该在哪里进行修改,答案来自我研究的Mock文档,链接在上面(这个页面)

解决办法是要在包含这个应用模型的模块的命名空间中模拟date类,像这样:

class TestDayOfWeek(TestCase):
    #Test the day of the week functions.

    # mock out the date class in the module that has already imported it via
    # from datetime import date, i.e. series.models (app_name.module_name)
    @mock.patch('series.models.date', FakeDate)
    def test_valid_my_next_day_of_week_sameday(self):
        from datetime import date
        FakeDate.today = classmethod(lambda cls: date(2011, 7, 3)) # July 3, 2011 is a Sunday

        new_day_of_week = DayOfWeek.objects.create()
        new_day_of_week.day = "SU"
        self.assertEquals(new_day_of_week.my_next_day_of_week(), date(2011, 7, 3))

希望这对其他人也能有所帮助!

撰写回答