跳转到主要内容

"django-test-plus 为Django的默认TestCase提供有用的扩展"

项目描述

django-test-plus

来自REVSYS的Django默认TestCase的有用扩展

pypi build matrix demo

理由

让我们面对现实,编写测试并不总是有趣。部分原因是需要编写大量样板代码。django-test-plus试图在编写Django测试时减少这些样板代码。我们保证这至少可以将您患上腕管综合征的时间推迟3周!

如果您想开始测试您的Django应用或提高团队测试的方式,我们提供TestStart来帮助您的团队显著提高生产力。

支持

支持:Python 3.8、3.9、3.10、3.11和3.12。

支持Django版本:3.2、4.2和5.0。

文档

完整的文档可在http://django-test-plus.readthedocs.org找到。

安装

$ pip install django-test-plus

使用

要使用 django-test-plus,请让您的测试从 test_plus.test.TestCase 继承,而不是正常的 django.test.TestCase:

from test_plus.test import TestCase

class MyViewTests(TestCase):
    ...

这足以让事情开始,但您被鼓励为项目创建自己的子类。这将允许您添加自己项目特定的辅助方法。

例如,如果您有一个名为 'myproject' 的 django 项目,您可以在 myproject/test.py 中创建以下内容

from test_plus.test import TestCase as PlusTestCase

class TestCase(PlusTestCase):
    pass

然后在您的测试中使用

from myproject.test import TestCase

class MyViewTests(TestCase):
    ...

此导入方式与导入 Django 的 TestCase 类似,也是有效的

from test_plus import TestCase

pytest 使用

现在,您可以请求 tp 来获取一个类似 TestCase 的 pytest fixture。以下所有方法现在都可以在 pytest 函数中使用。例如

def test_url_reverse(tp):
    expected_url = '/api/'
    reversed_url = tp.reverse('api')
    assert expected_url == reversed_url

tp_api fixture 将提供一个使用 django-rest-framework 的 APIClient()TestCase

def test_url_reverse(tp_api):
    response = tp_api.client.post("myapi", format="json")
    assert response.status_code == 200

方法

reverse(url_name, *args, **kwargs)

在测试视图时,您经常会发现自己需要反转 URL 的名称。在 django-test-plus 中,无需使用 from django.core.urlresolvers import reverse 烦琐代码。相反,使用

def test_something(self):
    url = self.reverse('my-url-name')
    slug_url = self.reverse('name-takes-a-slug', slug='my-slug')
    pk_url = self.reverse('name-takes-a-pk', pk=12)

如您所见,我们的 reverse 也会传递任何您需要传递的 args 或 kwargs。

get(url_name, follow=True, *args, **kwargs)

您还经常执行 HTTP GET URL 操作。我们的 get() 方法假设您正在传递一个带有任何需要反转 url_name 的 args 或 kwargs 的命名 URL。如果需要,将 TestClient.get() 的 kwargs 放入 'extra' 字典中。

def test_get_named_url(self):
    response = self.get('my-url-name')
    # Get XML data via AJAX request
    xml_response = self.get(
        'my-url-name',
        extra={'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'})

使用此 get 方法时,还有两件事为您自动完成:我们将最后的响应存储在 self.last_response 中,并将响应的上下文存储在 self.context 中。

因此,而不是

def test_default_django(self):
    response = self.client.get(reverse('my-url-name'))
    self.assertTrue('foo' in response.context)
    self.assertEqual(response.context['foo'], 12)

您可以编写

def test_testplus_get(self):
    self.get('my-url-name')
    self.assertInContext('foo')
    self.assertEqual(self.context['foo'], 12)

它也非常智能地处理已反转的 URL,因此您可以偷懒并这样做

def test_testplus_get(self):
    url = self.reverse('my-url-name')
    self.get(url)
    self.response_200()

如果您需要将查询字符串参数传递给 URL 名称,您可以这样做。假设名称 'search' 映射到 '/search/',那么

def test_testplus_get_query(self):
    self.get('search', data={'query': 'testing'})

将会发送 GET /search/?query=testing

post(url_name, data, follow=True, *args, **kwargs)

我们的 post() 方法接受一个命名 URL、一个可选的数据字典(您希望发布的任何数据)以及任何反转 url_name 所必需的 args 或 kwargs。如果需要,将 TestClient.post() 的 kwargs 放入 'extra' 字典中。

def test_post_named_url(self):
    response = self.post('my-url-name', data={'coolness-factor': 11.0},
                         extra={'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'})

注意 除了常用的 get 和 post 之外,我们还以相同的方式支持所有 HTTP 动词,例如 put、patch、head、trace、options 和 delete。

get_context(key)

您通常需要从模板上下文中获取内容

def test_context_data(self):
    self.get('my-view-with-some-context')
    slug = self.get_context('slug')

assertInContext(key)

您可以使用以下方法确保特定的键存在于最后的响应上下文中

def test_in_context(self):
    self.get('my-view-with-some-context')
    self.assertInContext('some-key')

assertContext(key, value)

我们可以获取上下文值并确保它们存在,同时我们还可以测试等价性。这断言 key == value

def test_in_context(self):
    self.get('my-view-with-some-context')
    self.assertContext('some-key', 'expected value')

assert_http_###_<status_name>(response, msg=None) - 状态码检查

您经常需要执行的其他测试之一是检查响应是否具有特定的 HTTP 状态码。使用 Django 的默认 TestCase,您会写

from django.core.urlresolvers import reverse

def test_status(self):
    response = self.client.get(reverse('my-url-name'))
    self.assertEqual(response.status_code, 200)

使用 django-test-plus,您可以将其缩短为

def test_better_status(self):
    response = self.get('my-url-name')
    self.assert_http_200_ok(response)

Django-test-plus 为您提供了大多数状态码断言。状态断言可以在它们自己的 mixin 中找到,如果您使用的是像 pycharm 这样的 IDE,则应该可以搜索。应该注意的是,在之前的版本中,django-test-plus 有着 response_###() 模式的断言方法,这些方法仍然可用,但已被弃用。以下是一些方法的列表。

每个断言方法都接受一个可选的 Django 测试客户端 response 和一个字符串 msg 参数,如果指定,将在发生失败时用作错误信息。方法 assert_http_301_moved_permanentlyassert_http_302_found 还接受一个可选的 url 参数,如果传递,将检查 response.url 是否匹配。

如果可用,assert_http_###_<status_name> 方法将使用最后一个响应。因此您可以这样做

def test_status(self):
    self.get('my-url-name')
    self.assert_http_200_ok()

这会稍微简短一些。

已经弃用但仍然可用的 response_###() 方法包括

  • response_200()
  • response_201()
  • response_204()
  • response_301()
  • response_302()
  • response_400()
  • response_401()
  • response_403()
  • response_404()
  • response_405()
  • response_409()
  • response_410()

所有这些方法都接受一个可选的 Django 测试客户端响应和一个 str msg 参数,如果指定,则在失败时用作错误消息。就像 assert_http_###_<status_name>() 方法一样,如果可用,这些方法将使用最后一个响应。

get_check_200(url_name, *args, **kwargs)

GET 和检查视图返回状态 200 是一个常见的测试。此方法使其更加方便:

def test_even_better_status(self):
    response = self.get_check_200('my-url-name')

make_user(username='testuser', password='password', perms=None)

在测试视图时,您经常需要创建各种用户以确保所有逻辑都是安全可靠的。为了使此过程更容易,此方法将为您创建一个用户

def test_user_stuff(self)
    user1 = self.make_user('u1')
    user2 = self.make_user('u2')

如果创建项目中的用户更复杂,例如,您从默认的 Django Auth 模型中删除了 username 字段,您可以提供一个 Factory Boy 工厂来创建它或覆盖此方法的自定义子类。

要使用 Factory Boy 工厂,创建您的类如下:

from test_plus.test import TestCase
from .factories import UserFactory


class MySpecialTest(TestCase):
    user_factory = UserFactory

    def test_special_creation(self):
        user1 = self.make_user('u1')

注意:此方法创建的用户默认将密码设置为字符串 'password',以简化测试。如果您需要特定的密码,可以覆盖 password 参数。

您还可以通过传递字符串 '<app_name>.<perm name>' 或 '<app_name>.*' 来传递用户权限。例如

user2 = self.make_user(perms=['myapp.create_widget', 'otherapp.*'])

print_form_errors(response_or_form=None)

在调试视图的失败测试时,此方法可以帮助您快速查看任何表单错误。

示例用法

class MyFormTest(TestCase):

    self.post('my-url-name', data={})
    self.print_form_errors()

    # or

    resp = self.post('my-url-name', data={})
    self.print_form_errors(resp)

    # or

    form = MyForm(data={})
    self.print_form_errors(form)

身份验证辅助工具

assertLoginRequired(url_name, *args, **kwargs)

此方法帮助您测试给定的命名 URL 是否需要授权

def test_auth(self):
    self.assertLoginRequired('my-restricted-url')
    self.assertLoginRequired('my-restricted-object', pk=12)
    self.assertLoginRequired('my-restricted-object', slug='something')

login() 上下文

除了确保视图需要登录和创建用户外,您接下来要做的是以各种用户登录以测试您的限制逻辑

def test_restrictions(self):
    user1 = self.make_user('u1')
    user2 = self.make_user('u2')

    self.assertLoginRequired('my-protected-view')

    with self.login(username=user1.username, password='password'):
        response = self.get('my-protected-view')
        # Test user1 sees what they should be seeing

    with self.login(username=user2.username, password='password'):
        response = self.get('my-protected-view')
        # Test user2 see what they should be seeing

由于我们很可能使用上面的 make_user() 创建用户,因此登录上下文假定密码是 'password',除非指定其他密码。因此您可以这样做

def test_restrictions(self):
    user1 = self.make_user('u1')

    with self.login(username=user1.username):
        response = self.get('my-protected-view')

如果我们使用 make_user(),我们还可以推导出用户名,这样我们可以进一步缩短如下:

def test_restrictions(self):
    user1 = self.make_user('u1')

    with self.login(user1):
        response = self.get('my-protected-view')

确保低查询计数

assertNumQueriesLessThan(number) - 上下文

Django 提供 assertNumQueries,这对于您的代码生成特定数量的查询非常有用。然而,如果由于数据本身的性质,这个数字会变化,使用此方法您仍然可以测试确保代码不会生成比预期多得多的查询。

def test_something_out(self):

    with self.assertNumQueriesLessThan(7):
        self.get('some-view-with-6-queries')

assertGoodView(url_name, *args, **kwargs)

此方法为您做了一些事情。它

  • 检索名称 URL
  • 确保视图不会生成超过 50 个查询
  • 确保响应状态码为 200
  • 返回响应

这种广泛的测试通常比没有测试要好。您可以使用它如下:

def test_better_than_nothing(self):
    response = self.assertGoodView('my-url-name')

测试 DRF 视图

为了利用 DRF 测试客户端的便利性,您可以创建 TestCase 的子类并设置 client_class 属性

from test_plus import TestCase
from rest_framework.test import APIClient


class APITestCase(TestCase):
    client_class = APIClient

为了方便起见,test_plus 附带 APITestCase,它就是这样做的

from test_plus import APITestCase


class MyAPITestCase(APITestCase):

    def test_post(self):
        data = {'testing': {'prop': 'value'}}
        self.post('view-json', data=data, extra={'format': 'json'})
        self.assert_http_200_ok()

请注意,使用 APITestCase 需要 Django >= 1.8 并已安装 django-rest-framework

测试基于类的“通用”视图

TestCase 方法的 get()post() 适用于基于函数和基于类的视图。然而,这样做会调用 Django 的 URL 解析、中间件、模板处理和装饰器系统。对于集成测试来说,这是有利的,因为您想要确保您的 URL 能够正确解析,视图权限得到执行等。但对于单元测试来说,这是昂贵的,因为这些 Django 请求/响应系统会与您的方法一起调用,并且通常不会影响最终结果。

基于类的视图(派生自 Django 的 generic.models.View 类)包含方法和混合,这使得细粒度的单元测试(更)可行。通常,您对通用视图类的使用涉及重写现有方法。当您真正想直接调用重写的方法并测试结果时,调用整个视图和 Django 请求/响应堆栈是浪费时间。

CBVTestCase 来拯救!

与上面的 TestCase 一样,让您的测试继承自 test_plus.test.CBVTestCase 而不是 TestCase,如下所示

from test_plus.test import CBVTestCase

class MyViewTests(CBVTestCase):

方法

get_instance(cls, initkwargs=None, request=None, *args, **kwargs)

这个核心方法简化了类的实例化,为您提供了一种直接调用类方法的方式。

返回 cls 的一个实例,用 initkwargs 初始化。在类实例上设置 requestargskwargs 属性。argskwargs 是您会传递给 reverse() 的相同值。

示例用法

from django.views import generic
from test_plus.test import CBVTestCase

class MyClass(generic.DetailView)

    def get_context_data(self, **kwargs):
        kwargs['answer'] = 42
        return kwargs

class MyTests(CBVTestCase):

    def test_context_data(self):
        my_view = self.get_instance(MyClass, {'object': some_object})
        context = my_view.get_context_data()
        self.assertEqual(context['answer'], 42)

get(cls, initkwargs=None, *args, **kwargs)

调用 cls.get() 并返回响应,如果可能的话渲染模板。基于 CBVTestCase.get_instance() 的基础。

所有 test_plus.test.TestCase 方法都是有效的,所以以下也有效

response = self.get(MyClass)
self.assertContext('my_key', expected_value)

所有 test_plus TestCase 的副作用都被尊重,并且所有 test_plus TestCase 断言方法都与 CBVTestCase.get() 一起工作。

注意:此方法绕过了 Django 的中间件,因此中间件创建的上下文变量不可用。如果这影响了您的模板/上下文测试,您应使用 TestCase 而不是 CBVTestCase。

post(cls, data=None, initkwargs=None, *args, **kwargs)

调用 cls.post() 并返回响应,如果可能的话渲染模板。基于 CBVTestCase.get_instance() 的基础。

示例

response = self.post(MyClass, data={'search_term': 'revsys'})
self.response_200(response)
self.assertContext('company_name', 'RevSys')

所有 test_plus TestCase 的副作用都被尊重,并且所有 test_plus TestCase 断言方法都与 CBVTestCase.post() 一起工作。

注意:此方法绕过了 Django 的中间件,因此中间件创建的上下文变量不可用。如果这影响了您的模板/上下文测试,您应使用 TestCase 而不是 CBVTestCase。

get_check_200(cls, initkwargs=None, *args, **kwargs)

TestCase.get_check_200() 一样工作。调用者必须提供视图类而不是 URL 名称或路径参数。

所有 test_plus TestCase 的副作用都被尊重,并且所有 test_plus TestCase 断言方法都与 CBVTestCase.post() 一起工作。

assertGoodView(cls, initkwargs=None, *args, **kwargs)

TestCase.assertGoodView() 一样工作。调用者必须提供视图类而不是 URL 名称或路径参数。

所有 test_plus TestCase 的副作用都被尊重,并且所有 test_plus TestCase 断言方法都与 CBVTestCase.post() 一起工作。

开发

要处理 django-test-plus 本身,克隆此存储库并运行以下命令

$ pip install -e .
$ pip install -e .[test]

运行所有测试

$ nox

注意:您还需要确保位于此存储库根目录的 test_project 目录位于您的虚拟环境路径中。

保持联系!

如果您对此项目有任何疑问,请打开 GitHub 问题。如果您喜欢我们并想跟踪我们的动态,以下是我们可以在网上找到的地方

项目详情


下载文件

下载适用于您的平台的文件。如果您不确定选择哪一个,请了解更多关于安装包的信息。

源代码分布

django-test-plus-2.2.4.tar.gz (18.9 kB 查看哈希值)

上传时间 源代码

构建分布

django_test_plus-2.2.4-py3-none-any.whl (17.5 kB 查看哈希值)

上传时间 Python 3