简体   繁体   English

在 DRF 中测试节流的正确方法是什么?

[英]What is the proper way of testing throttling in DRF?

What is the proper way of testing throttling in DRF?在 DRF 中测试节流的正确方法是什么? I coulnd't find out any answer to this question on the net.我在网上找不到这个问题的任何答案。 I want to have separate tests for each endpoint since each one has custom requests limits (ScopedRateThrottle).我想对每个端点进行单独的测试,因为每个端点都有自定义请求限制(ScopedRateThrottle)。

The important thing is that it can't affect other tests - they have to somehow run without throttling and limiting.重要的是它不能影响其他测试——它们必须以某种方式运行而没有节流和限制。

Like people already mentioned, this doesn't exactly fall within the scope of unit tests, but still, how about simply doing something like this:就像人们已经提到的那样,这并不完全属于单元测试的范围,但是,简单地做这样的事情怎么样:

from django.core.urlresolvers import reverse
from django.test import override_settings
from rest_framework.test import APITestCase, APIClient


class ThrottleApiTests(APITestCase):
    # make sure to override your settings for testing
    TESTING_THRESHOLD = '5/min'
    # THROTTLE_THRESHOLD is the variable that you set for DRF DEFAULT_THROTTLE_RATES
    @override_settings(THROTTLE_THRESHOLD=TESTING_THRESHOLD)
    def test_check_health(self):
        client = APIClient()
        # some end point you want to test (in this case it's a public enpoint that doesn't require authentication
        _url = reverse('check-health')
        # this is probably set in settings in you case
        for i in range(0, self.TESTING_THRESHOLD):
            client.get(_url)

        # this call should err
        response = client.get(_url)
        # 429 - too many requests
        self.assertEqual(response.status_code, 429)

Also, regarding your concerns of side-effects, as long as you do user creation in setUp or setUpTestData , tests will be isolated (as they should), so no need to worry about 'dirty' data or scope in that sense.此外,关于您对副作用的担忧,只要您在setUpsetUpTestData中创建用户,测试就会被隔离(应该如此),因此在这个意义上无需担心“脏”数据或范围。

Regarding cache clearing between tests , I would just add cache.clear() in tearDown or try and clear the specific key defined for throttling .关于测试之间的缓存清除,我只需在tearDown中添加cache.clear()或尝试清除为限制定义的特定键

An easy solution is to patch the get_rate method of your throttle class.一个简单的解决方案是patch油门类的get_rate方法。 Thanks to tprestegard for this comment !感谢tprestegard 的评论

I have a custom class in my case:我有一个自定义类:

from rest_framework.throttling import UserRateThrottle

class AuthRateThrottle(UserRateThrottle):
    scope = 'auth'

In your tests:在您的测试中:

from unittest.mock import patch
from django.core.cache import cache
from rest_framework import status

class Tests(SimpleTestCase):
    def setUp(self):
        cache.clear()

    @patch('path.to.AuthRateThrottle.get_rate')
    def test_throttling(self, mock):
        mock.return_value = '1/day'
        response = self.client.post(self.url, {})
        self.assertEqual(
            response.status_code,
            status.HTTP_400_BAD_REQUEST,  # some fields are required
        )
        response = self.client.post(self.url, {})
        self.assertEqual(
            response.status_code,
            status.HTTP_429_TOO_MANY_REQUESTS,
        )

It is also possible to patch the method in the DRF package to change the behavior of the standard throttle classes: @patch('rest_framework.throttling.SimpleRateThrottle.get_rate')也可以修补 DRF 包中的方法以更改标准油门类的行为: @patch('rest_framework.throttling.SimpleRateThrottle.get_rate')

I implemented my own caching mechanism for throttling based on the user and the parameters with which a request is called.我实现了自己的缓存机制,用于根据用户和调用请求的参数进行限制。 You can override SimpleRateThrottle.get_cache_key to get this behavior.您可以覆盖SimpleRateThrottle.get_cache_key以获取此行为。

Take this throttle class for example:以这个油门类为例:

class YourCustomThrottleClass(SimpleRateThrottle):
    rate = "1/d"
    scope = "your-custom-throttle"

    def get_cache_key(self, request: Request, view: viewsets.ModelViewSet):
        # we want to throttle the based on the request user as well as the parameter
        # `foo` (i.e. the user can make a request with a different `foo` as many times
        # as they want in a day, but only once a day for a given `foo`).
        foo_request_param = view.kwargs["foo"]
        ident = f"{request.user.pk}_{foo_request_param}"
        # below format is copied from `UserRateThrottle.get_cache_key`.
        return self.cache_format % {"scope": self.scope, "ident": ident}

In order to clear this in a TestCase I call the following method in each test method as required:为了在TestCase中清除这一点,我根据需要在每个测试方法中调用以下方法:

def _clear_throttle_cache(self, request_user, foo_param):
    # we need to clear the cache of the throttle limits already stored there.
    throttle = YourCustomThrottleClass()

    # in the below two lines mock whatever attributes on the request and
    # view instances are used to calculate the cache key in `.get_cache_key`
    # which you overrode. Here we use `request.user` and `view.kwargs["foo"]` 
    # to calculate the throttle key, so we mock those.
    pretend_view = MagicMock(kwargs={foo: foo_param})
    pretend_request = MagicMock(user=request_user)
    
    # this is the method you overrode in `YourCustomThrottleClass`.
    throttle_key = throttle.get_cache_key(pretend_request, pretend_view)
    throttle.cache.delete(user_key)

This is an amendment on yofee's post which got me 90% there.这是对yofee 帖子的修改,它让我达到了 90%。 When using a throttle, custom or otherwise, with a set rate , get_rate is never called.当使用带有设置rate的自定义或其他方式的节流阀时,永远不会调用get_rate As shown below from the source .源码如下图。

    def __init__(self):
        if not getattr(self, 'rate', None):
            self.rate = self.get_rate()

Hence when one is mocking a throttle with a set rate that is not None , I would recommend patching the rate attribute directly.因此,当 mocking 是一个设置速率不是None的节流阀时,我建议直接修补速率属性。

...
with mock.patch.object(AuthRateThrottle, 'rate', '1/day'):
...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM