Django REST framework - 测试

“没有测试的代码,从设计上就是有缺陷的。”——Jacob Kaplan-Moss

REST framework 包含了一些扩展 Django 现有测试框架的辅助类,从而改进了对发出 API 请求的支持。

APIRequestFactory

扩展了 Django 现有的 RequestFactory 类。

创建测试请求

APIRequestFactory 类支持与 Django 标准 RequestFactory 类几乎相同的 API 。这意味着标准的 .get().post().put().patch().delete().head().options() 方法都可以使用。

from rest_framework.test import APIRequestFactory

# 使用标准的 RequestFactory API 创建一个表单 POST 请求
factory = APIRequestFactory()
request = factory.post('/notes/', {'title': 'new idea'})

# 使用标准的 RequestFactory API 编码 JSON 数据
request = factory.post('/notes/', {'title': 'new idea'}, content_type='application/json')

使用 format 参数

postputpatch 这样会创建请求体的方法,包含一个 format 参数,这使得使用多种请求格式生成请求变得容易。当使用这个参数时,工厂会选择合适的渲染器及其配置的 content_type 。例如:

# 创建一个 JSON POST 请求
factory = APIRequestFactory()
request = factory.post('/notes/', {'title': 'new idea'}, format='json')

默认情况下,可用的格式是 'multipart''json' 。为了与 Django 现有的 RequestFactory 兼容,默认格式是 'multipart'

为了支持更广泛的请求格式,或更改默认格式,请参阅配置部分。

显式编码请求体

如果你需要显式编码请求体,可以通过设置 content_type 标志来实现。例如:

request = factory.post('/notes/', yaml.dump({'title': 'new idea'}), content_type='application/yaml')

使用表单数据进行 PUT 和 PATCH 操作

APIRequestFactory 和 Django 的 RequestFactory 之间一个值得注意的区别是,对于不仅仅是 .post() 的方法,也会对多部件表单数据进行编码。

例如,使用 APIRequestFactory,你可以像这样进行表单 PUT 请求:

factory = APIRequestFactory()
request = factory.put('/notes/547/', {'title': 'remember to email dave'})

使用 Django 的 RequestFactory,你需要自己显式编码数据:

from django.test.client import encode_multipart, RequestFactory

factory = RequestFactory()
data = {'title': 'remember to email dave'}
content = encode_multipart('BoUnDaRyStRiNg', data)
content_type = 'multipart/form-data; boundary=BoUnDaRyStRiNg'
request = factory.put('/notes/547/', content, content_type=content_type)

强制认证

当使用请求工厂直接测试视图时,通常方便能够直接认证请求,而不是必须构造正确的认证凭据。

要强制认证请求,使用 force_authenticate() 方法。

from rest_framework.test import force_authenticate

factory = APIRequestFactory()
user = User.objects.get(username='olivia')
view = AccountDetail.as_view()

# 发出一个经过认证的请求到视图...
request = factory.get('/accounts/django-superstars/')
force_authenticate(request, user=user)
response = view(request)

该方法的签名是 force_authenticate(request, user=None, token=None) 。当调用时,可以设置用户和 / 或令牌。

例如,当你使用令牌强制认证时,可以这样做:

user = User.objects.get(username='olivia')
request = factory.get('/accounts/django-superstars/')
force_authenticate(request, user=user, token=user.auth_token)

注意force_authenticate 直接将 request.user 设置为内存中的 user 实例。如果你在多个测试中使用同一个 user 实例,并且这些测试会更新已保存的 user 状态,那么你可能需要在测试之间调用 refresh_from_db()

注意 :当你使用 APIRequestFactory 时,返回的对象是 Django 标准的 HttpRequest,而不是 REST framework 的 Request 对象,后者只有在调用视图时才会生成。这意味着直接在请求对象上设置属性可能不会总是产生你期望的效果。例如,直接设置 .token 将不会起作用,而直接设置 .user 只有在使用会话认证时才会起作用。

# 只有当使用 `SessionAuthentication` 时,请求才会被认证。
request = factory.get('/accounts/django-superstars/')
request.user = user
response = view(request)

强制 CSRF 验证

默认情况下,当将由 APIRequestFactory 创建的请求传递给 REST framework 视图时,不会应用 CSRF 验证。如果你需要显式地开启 CSRF 验证,可以在实例化工厂时设置 enforce_csrf_checks 标志。

factory = APIRequestFactory(enforce_csrf_checks=True)

注意 :值得注意的是,Django 的标准 RequestFactory 不需要包含这个选项,因为在常规 Django 使用中,CSRF 验证是在中间件中进行的,而中间件在直接测试视图时不会运行。当使用 REST framework 时,CSRF 验证是在视图内部进行的,因此请求工厂需要禁用视图级别的 CSRF 检查。

APIClient

扩展了 Django 现有的 Client 类。

发出请求

APIClient 类支持与 Django 标准 Client 类相同的请求接口。这意味着标准的 .get().post().put().patch().delete().head().options() 方法都可以使用。例如:

from rest_framework.test import APIClient

client = APIClient()
client.post('/notes/', {'title': 'new idea'}, format='json')

为了支持更广泛的请求格式,或更改默认格式,请参阅配置部分。

认证

.login(**kwargs*)

login 方法的功能与 Django 标准 Client 类中的完全相同。这允许你针对包含 SessionAuthentication 的任何视图认证请求。

# 在登录会话的上下文中发出所有请求。
client = APIClient()
client.login(username='lauren', password='secret')

要登出,像往常一样调用 logout 方法。

# 登出
client.logout()

当使用会话认证时,例如在包含 AJAX 交互的网站中,login 方法是合适的。

.credentials(**kwargs*)

credentials 方法可用于设置将在测试客户端后续发出的所有请求中包含的头部。

from rest_framework.authtoken.models import Token
from rest_framework.test import APIClient

# 在所有请求中包含适当的 `Authorization:` 头部。
token = Token.objects.get(user__username='lauren')
client = APIClient()
client.credentials(HTTP_AUTHORIZATION='Token ' + token.key)

注意,第二次调用 credentials 将覆盖任何现有的凭据。你可以通过不带任何参数调用该方法来清除任何现有的凭据。

# 停止包含任何凭据
client.credentials()

credentials 方法适用于测试需要认证头部的 API,例如基本认证、OAuth1a 和 OAuth2 认证,以及简单的令牌认证方案。

.force_authenticate(user=None, token=None)

有时你可能希望绕过认证,并强制测试客户端发出的所有请求都被自动视为已认证。这在测试 API 但不想为了发出测试请求而构造有效认证凭据时,可以是一个有用的快捷方式。

user = User.objects.get(username='lauren')
client = APIClient()
client.force_authenticate(user=user)

要取消后续请求的认证,调用 force_authenticate 并将用户和 / 或令牌设置为 None

client.force_authenticate(user=None)

CSRF 验证

默认情况下,当使用 APIClient 时不会应用 CSRF 验证。如果你需要显式地开启 CSRF 验证,可以在实例化客户端时设置 enforce_csrf_checks 标志。

client = APIClient(enforce_csrf_checks=True)

像往常一样,CSRF 验证只会应用于通过调用 login() 登录的会话认证视图。这意味着只有当客户端通过 login() 登录时,CSRF 验证才会发生。

RequestsClient

REST framework 还包含一个客户端,用于使用流行的 Python 库 requests 与你的应用程序进行交互。这在以下情况下可能很有用:

  • 你期望主要从另一个 Python 服务与 API 进行交互,并希望以客户端将看到的级别来测试服务。
  • 你希望编写可以针对开发环境、预生产环境或生产环境运行的测试。(参阅 “实时测试” 。)

这暴露了与直接使用 requests 会话完全相同的接口。

from rest_framework.test import RequestsClient

client = RequestsClient()
response = client.get('http://testserver/users/')
assert response.status_code == 200

注意,requests 客户端要求你传递完整的 URL。

RequestsClient 和数据库操作

RequestsClient 类在你希望编写仅通过服务接口进行交互的测试时非常有用。这比使用标准的 Django 测试客户端要严格一些,因为你必须通过 API 进行所有交互。

如果你正在使用 RequestsClient,你将希望确保测试设置和结果断言是通过常规 API 调用执行的,而不是直接与数据库模型进行交互。例如,而不是检查 Customer.objects.count() == 3,你会列出客户端点,并确保它包含三个记录。

可以像使用标准的 requests.Session 实例一样提供自定义头部和认证凭据。

from requests.auth import HTTPBasicAuth

client.auth = HTTPBasicAuth('user', 'pass')
client.headers.update({'x-test': 'true'})

CSRF

如果你正在使用 SessionAuthentication,则对于任何 POSTPUTPATCHDELETE 请求,你需要包含一个 CSRF 令牌。

你可以通过遵循 JavaScript 客户端将使用的相同流程来实现。首先,通过 GET 请求获取 CSRF 令牌,然后在后续请求中呈现该令牌。

例如...

client = RequestsClient()

# 获取 CSRF 令牌。
response = client.get('http://testserver/homepage/')
assert response.status_code == 200
csrftoken = response.cookies['csrftoken']

# 与 API 交互。
response = client.post('http://testserver/organisations/', json={
    'name': 'MegaCorp',
    'status': 'active'
}, headers={'X-CSRFToken': csrftoken})
assert response.status_code == 200

实时测试

如果谨慎使用,RequestsClientCoreAPIClient 都提供了编写可以在开发环境中运行,或者直接针对你的预生产服务器或生产环境运行的测试用例的能力。

使用这种风格来创建一些核心功能的基本测试是一种强大的方式来验证你的实时服务。这样做可能需要一些谨慎的设置和清理,以确保测试以不会直接影响客户数据的方式运行。

CoreAPIClient

CoreAPIClient 允许你使用 Python coreapi 客户端库与你的 API 进行交互。

# 获取 API 架构
client = CoreAPIClient()
schema = client.get('http://testserver/schema/')

# 创建一个新组织
params = {'name': 'MegaCorp', 'status': 'active'}
client.action(schema, ['organisations', 'create'], params)

# 确保组织存在于列表中
data = client.action(schema, ['organisations', 'list'])
assert(len(data) == 1)
assert(data == [{'name': 'MegaCorp', 'status': 'active'}])

可以像使用 RequestsClient 一样,在 CoreAPIClient 中使用自定义头部和认证。

from requests.auth import HTTPBasicAuth

client = CoreAPIClient()
client.session.auth = HTTPBasicAuth('user', 'pass')
client.session.headers.update({'x-test': 'true'})

API 测试用例

REST framework 包含以下测试用例类,它们与 Django 现有的测试用例类相对应,但使用 APIClient 而不是 Django 默认的 Client

  • APISimpleTestCase
  • APITransactionTestCase
  • APITestCase
  • APILiveServerTestCase

示例

你可以像使用常规的 Django 测试用例类一样使用 REST framework 的任何测试用例类。self.client 属性将是一个 APIClient 实例。

from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from myproject.apps.core.models import Account

class AccountTests(APITestCase):
    def test_create_account(self):
        """
        确保我们能够创建一个新的账户对象。
        """
        url = reverse('account-list')
        data = {'name': 'DabApps'}
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(Account.objects.count(), 1)
        self.assertEqual(Account.objects.get().name, 'DabApps')

URLPatternsTestCase

REST framework 还提供了一个测试用例类,用于在每个类的基础上隔离 urlpatterns 。注意,它继承自 Django 的 SimpleTestCase,并且很可能需要与另一个测试用例类混合使用。

示例

from django.urls import include, path, reverse
from rest_framework.test import APITestCase, URLPatternsTestCase

class AccountTests(APITestCase, URLPatternsTestCase):
    urlpatterns = [
        path('api/', include('api.urls')),
    ]

    def test_create_account(self):
        """
        确保我们能够创建一个新的账户对象。
        """
        url = reverse('account-list')
        response = self.client.get(url, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 1)

测试响应

检查响应数据

在检查测试响应的有效性时,通常更方便的是检查创建响应时所使用数据,而不是检查完全渲染后的响应。

例如,检查 response.data 更容易:

response = self.client.get('/users/4/')
self.assertEqual(response.data, {'id': 4, 'username': 'lauren'})

而不是检查解析 response.content 的结果:

response = self.client.get('/users/4/')
self.assertEqual(json.loads(response.content), {'id': 4, 'username': 'lauren'})

渲染响应

如果你正在使用 APIRequestFactory 直接测试视图,返回的响应尚未渲染,因为 Django 内部的请求 - 响应周期会执行模板响应的渲染。为了访问 response.content,你首先需要渲染响应。

view = UserDetail.as_view()
request = factory.get('/users/4')
response = view(request, pk='4')
response.render()  # 在访问 `response.content` 之前必须这样做。
self.assertEqual(response.content, '{"username": "lauren", "id": 4}')

配置

设置默认格式

可以通过 TEST_REQUEST_DEFAULT_FORMAT 设置键来设置用于测试请求的默认格式。例如,为了在测试请求中始终使用 JSON 而不是默认的多部件表单请求,可以在你的 settings.py 文件中设置以下内容:

REST_FRAMEWORK = {
    ...
    'TEST_REQUEST_DEFAULT_FORMAT': 'json'
}

设置可用格式

如果你需要使用多部件或 JSON 请求以外的格式进行测试,可以通过设置 TEST_REQUEST_RENDERER_CLASSES 设置来实现。

例如,为了在测试请求中添加对 format='html' 的支持,你的 settings.py 文件中可能包含类似这样的内容:

REST_FRAMEWORK = {
    ...
    'TEST_REQUEST_RENDERER_CLASSES': [
        'rest_framework.renderers.MultiPartRenderer',
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.TemplateHTMLRenderer'
    ]
}

你可能感兴趣的:(djangopython)