【问题标题】:Testing Python Decorators?测试 Python 装饰器?
【发布时间】:2011-02-13 21:11:17
【问题描述】:

我正在为一个 Django 项目编写一些单元测试,我想知道是否可以(或有必要?)测试我为它编写的一些装饰器。

这是我编写的装饰器示例:

class login_required(object):

    def __init__(self, f):
        self.f = f

    def __call__(self, *args):
        request = args[0]
        if request.user and request.user.is_authenticated():
            return self.f(*args)
        return redirect('/login')

【问题讨论】:

  • 我认为这是一个很好的问题,但你为什么要重写这个装饰器呢? docs.djangoproject.com/en/1.1/topics/auth/…
  • 您没有使用 django.contrib.auth.decorators.login_required 的任何特殊原因?
  • 虽然装饰器只是一段代码,但它们应该像任何其他代码一样进行测试。
  • 对@Mark Lavin 的简短回答是,我们正在推出我们自己的身份验证系统,并且我们正在模仿 django.conrtib.auth 中的装饰器来让我们的生活更轻松

标签: python django unit-testing decorator python-unittest


【解决方案1】:

这样的装饰器可以简单地通过鸭子类型进行测试。只需向 call 函数提供一个模拟对象,该对象似乎保留并充当请求,然后查看您是否获得预期的行为。

当需要使用单元测试时,我会说是非常个人化的。您提供的示例包含这样的基本代码,人们可能会说它没有必要。但话又说回来,测试这样一个类的成本同样低。

【讨论】:

    【解决方案2】:

    简单地说:

    from nose.tools import assert_equal
    from mock import Mock
    
    class TestLoginRequired(object):
        def test_no_user(self):
            func = Mock()
            decorated_func = login_required(func)
            request = prepare_request_without_user()
            response = decorated_func(request)
            assert not func.called
            # assert response is redirect
    
        def test_bad_user(self):
            func = Mock()
            decorated_func = login_required(func)
            request = prepare_request_with_non_authenticated_user()
            response = decorated_func(request)
            assert not func.called
            # assert response is redirect
    
        def test_ok(self):
            func = Mock(return_value='my response')
            decorated_func = login_required(func)
            request = prepare_request_with_ok_user()
            response = decorated_func(request)
            func.assert_called_with(request)
            assert_equal(response, 'my response')
    

    mock 库在这里提供帮助。

    【讨论】:

      【解决方案3】:

      Django 的单元测试示例

      class TestCaseExample(TestCase):
          def test_decorator(self):
              request = HttpRequest()
              # Set the required properties of your request
              function = lambda x: x
              decorator = login_required(function)
              response = decorator(request)
              self.assertRedirects(response)
      

      总的来说,我使用的方法如下:

      1. 设置您的请求。
      2. 创建一个虚拟函数以允许装饰器魔法发生 (lambda)。您可以在此处控制最终将传递给装饰器的参数数量。
      3. 根据装饰器的响应执行断言。

      【讨论】:

      • 你有 1 个错误,TypeError: assertRedirects() missing 1 required positional argument: 'expected_url'
      【解决方案4】:

      对于那些寻找 django 类型装饰器测试的人,这是我在自定义 django 装饰器上运行测试的方式:

      common/decorators.py

      from functools import wraps
      from django.http import Http404
      
      
      def condition_passes_test(test_func, fail_msg=''):
          """
          Decorator for views that checks that a condition passes the given test,
          raising a 404 if condition fails
          """
      
          def decorator(view_func):
              @wraps(view_func)
              def _wrapped_view(request, *args, **kwargs):
                  if test_func():
                      return view_func(request, *args, **kwargs)
                  else:
                      raise Http404(fail_msg)
              return _wrapped_view
          return decorator
      

      测试

      import django
      from django.test import TestCase
      from django.http import Http404
      from django.http import HttpResponse
      from django.test import RequestFactory
      
      from common import decorators
      
      
      class TestCommonDecorators(TestCase):
      
          def shortDescription(self):
              return None
      
          def test_condition_passes_test1(self):
              """
              Test case where we raise a 404 b/c test function returns False
              """
              def func():
                  return False
      
              @decorators.condition_passes_test(func)
              def a_view(request):
                  return HttpResponse('a response for request {}'.format(request))
              request = RequestFactory().get('/a/url')
      
              with self.assertRaises(django.http.response.Http404):
                  a_view(request)
      
          def test_condition_passes_test2(self):
              """
              Test case where we get 200 b/c test function returns True
              """
              def func():
                  return True
      
              @decorators.condition_passes_test(func)
              def a_view(request):
                  return HttpResponse('a response for request {}'.format(request))
      
              request = RequestFactory().get('/app_health/z')
              request = a_view(request)
              self.assertEquals(request.status_code, 200)
      

      注意:我在我的观点上使用它,my_test_function 返回TrueFalse

      @method_decorator(condition_passes_test(my_test_function, fail_msg="Container view disabled"), name='dispatch')
      

      【讨论】:

        猜你喜欢
        • 2017-08-20
        • 2012-11-01
        • 2014-04-28
        • 2012-04-26
        • 2011-12-20
        • 1970-01-01
        • 2019-02-21
        • 2016-08-20
        • 2019-07-29
        相关资源
        最近更新 更多