7

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

测试类:

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)

调试后,似乎timeDjangoRestFramework 中使用的模块throttling.UserRateThrottle以某种方式给出了一个值,该值始终领先于我的测试中的冻结时间,这导致了几秒钟的精度问题。

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

一旦调用了装饰器或上下文管理器,所有对 datetime.datetime.now()、datetime.datetime.utcnow()、datetime.date.today()、time.time()、time.localtime()、time 的调用.gmtime() 和 time.strftime() 将返回已冻结的时间。

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

我尝试单独使用模块来模拟time.time使用,但仍然没有解决问题。UserRateThrottlemock

----> 知道可能是什么问题,如何解决?

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

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

DRF 类源代码

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
        ....
4

2 回答 2

2

您需要SimpleRateThrottle使用 FreezeGun 的 time.time 覆盖 ' 计时器。

这里发生的情况是,feezegun 可能会覆盖 Python 的时间模块。但是,SimpleRateThrottle不使用模块,它使用模块的功能,这是冷冻枪无法触及的。

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

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

former_timer = SimpleRateThrottle.timer
SimpleRateThrottle.timer = time.time

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

SimpleRateThrottle.timer = former_timer

请注意,您还可以使用猴子补丁库来为您处理。

于 2017-02-01T13:53:08.913 回答
1

如果你使用 pytest,你可以这样做:

import pytest
from time import time
from rest_framework.throttling import SimpleRateThrottle


@pytest.mark.freeze_time('2020-09-23 00:00:00')
def test_something(monkeypatch):
    monkeypatch.setattr(SimpleRateThrottle, 'timer', lambda _: time())

    # … unit test here …
于 2020-09-23T12:45:05.970 回答