关于python:django测试中的模拟时间问题:时间似乎不能使用freezegun冻结

Mocking time issue in django test: time seems not to be frozen using freezegun

我在这里写一个功能测试来检查我的API限制是否按预期工作(将在每个月的开始时休息)。

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class ApiThrottlingTest(ThrottlingBaseTest):

    def test_throttling_purchaser_case(self):

        now = datetime.datetime(year=2015, month=1, day=10, hour=6, minute=6, second=3)

        last_day_of_current_month = datetime.datetime(year=2015, month=1, day=31, hour=23, minute=59, second=59)

        first_day_of_next_month = datetime.datetime(year=2015, month=2, day=1, hour=0, minute=0, second=0)

        with freeze_time(now) as frozen_datetime:
            for i in xrange(3):
                resp = self._project_details_request()
                self.assertEqual(resp.status_code, 200)

            resp = self._project_details_request()
            self.assertEqual(resp.status_code, 429)

            frozen_datetime.move_to(last_day_of_current_month)
            resp = self._project_details_request()
            # the test fails at this level
            self.assertEqual(resp.status_code, 429)

            frozen_datetime.move_to(first_day_of_next_month)
            resp = self._project_details_request()
            self.assertEqual(resp.status_code, 200)

如果符合以下条件,则测试工作正常:last_day_of_current_month = datetime.datetime(... second=0)
但如果:last_day_of_current_month = datetime.datetime(... second=59)将失败

在调试之后,似乎DjangoRestFramework throttling.UserRateThrottle中使用的time模块在某种程度上给出的值始终超过我测试中的前端时间,这导致了几秒钟的精度问题。

基于FreezeGun Doc time.time()也应该被冻结:

Once the decorator or context manager have been invoked, all calls to datetime.datetime.now(), datetime.datetime.utcnow(), datetime.date.today(), time.time(), time.localtime(), time.gmtime(), and time.strftime() will return the time that has been frozen.

但看起来我的情况time.time正确地取得了模拟日期时间的开始时间,但随后随着时间的推移不断变化,这是预期的,它会被冻结,直到手动转发时间。

我尝试使用mock模块单独模拟UserRateThrottle中使用的time.time,但仍未解决问题。

---->任何想法可能是什么问题,怎么可能解决?

测试失败:(在时间转发到该月的最后一天之后:第14行)

self.assertEqual(resp.status_code, 429)
AssertionError: 200 != 429

DRF类源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class SimpleRateThrottle(BaseThrottle):
    ...

    cache = default_cache
    timer = time.time
    cache_format = 'throttle_%(scope)s_%(ident)s'

    def __init__(self):
       ....

    def allow_request(self, request, view):
        ...

        self.now = self.timer() # here timer() returns unexpected value in test
        ....

你需要用FreezeGun的time.time覆盖SimpleRateThrottle的计时器。

这里发生的事情是,feezegun可能会覆盖Python的时间模块。 但是,SimpleRateThrottle不使用模块,它使用模块的功能,使其远离冷冻枪。

因此,SimpleRateThrottle使用Python标准库时间模块,而代码的其他部分使用freezegun的那个。

编辑:
你应该这样做 - 在FreezeGun被激活之后:

1
2
former_timer = SimpleRateThrottle.timer
SimpleRateThrottle.timer = time.time

一旦你的测试结束(在tearDown或任何等价物中):

1
SimpleRateThrottle.timer = former_timer

请注意,您也可以使用猴子修补lib来为您处理。