测试工具 — Django 文档
测试工具
Django 提供了一小组在编写测试时派上用场的工具。
测试客户端
测试客户端是一个 Python 类,充当虚拟 Web 浏览器,允许您测试视图并以编程方式与 Django 驱动的应用程序交互。
你可以用测试客户端做的一些事情是:
- 在 URL 上模拟 GET 和 POST 请求并观察响应——从低级 HTTP(结果标头和状态代码)到页面内容的所有内容。
- 查看重定向链(如果有)并检查每一步的 URL 和状态代码。
- 测试给定的请求是否由给定的 Django 模板呈现,模板上下文包含某些值。
请注意,测试客户端并非旨在替代 Selenium 或其他“浏览器内”框架。 Django 的测试客户端有不同的侧重点。 简而言之:
- 使用 Django 的测试客户端来确定正在呈现正确的模板并且向模板传递了正确的上下文数据。
- 使用 Selenium 等浏览器内框架来测试 渲染的 HTML 和网页的 行为 ,即 JavaScript 功能。 Django 还为这些框架提供了特殊支持; 有关更多详细信息,请参阅 LiveServerTestCase 部分。
综合测试套件应结合使用两种测试类型。
概述和快速示例
要使用测试客户端,请实例化 django.test.Client
并检索网页:
>>> from django.test import Client
>>> c = Client()
>>> response = c.post('/login/', {'username': 'john', 'password': 'smith'})
>>> response.status_code
200
>>> response = c.get('/customer/details/')
>>> response.content
b'<!DOCTYPE html...'
正如此示例所建议的,您可以从 Python 交互式解释器的会话中实例化 Client
。
请注意有关测试客户端如何工作的一些重要事项:
测试客户端 不 要求运行 Web 服务器。 事实上,它会在没有任何 Web 服务器运行的情况下运行得很好! 那是因为它避免了 HTTP 的开销,直接与 Django 框架打交道。 这有助于使单元测试快速运行。
检索页面时,请记住指定 URL 的 路径 ,而不是整个域。 例如,这是正确的:
>>> c.get('/login/')
这是不正确的:
>>> c.get('https://www.example.com/login/')
测试客户端无法检索不是由您的 Django 项目提供支持的网页。 如果您需要检索其他网页,请使用 Python 标准库模块,例如
urllib
。为了解析 URL,测试客户端使用 :setting:`ROOT_URLCONF` 设置指向的任何 URLconf。
虽然上面的例子可以在 Python 交互式解释器中工作,但一些测试客户端的功能,特别是与模板相关的功能,只有在测试运行时才可用 '。
这样做的原因是 Django 的测试运行器执行了一些黑魔法,以确定给定视图加载了哪个模板。 这种黑魔法(本质上是在内存中修补 Django 的模板系统)仅在测试运行期间发生。
默认情况下,测试客户端将禁用您的站点执行的任何 CSRF 检查。
如果由于某种原因,您 希望 测试客户端执行 CSRF 检查,您可以创建一个测试客户端的实例来强制执行 CSRF 检查。 为此,请在构建客户端时传入
enforce_csrf_checks
参数:>>> from django.test import Client >>> csrf_client = Client(enforce_csrf_checks=True)
提出要求
使用 django.test.Client
类发出请求。
- class Client(enforce_csrf_checks=False, json_encoder=DjangoJSONEncoder, **defaults)
它在构建时不需要参数。 但是,您可以使用关键字参数来指定一些默认标头。 例如,这将在每个请求中发送一个
User-Agent
HTTP 标头:>>> c = Client(HTTP_USER_AGENT='Mozilla/5.0')
来自
extra
关键字参数的值传递给 get()、post() 等。 优先于传递给类构造函数的默认值。enforce_csrf_checks
参数可用于测试 CSRF 保护(见上文)。json_encoder
参数允许为 post() 中描述的 JSON 序列化设置自定义 JSON 编码器。raise_request_exception
参数允许控制在请求期间引发的异常是否也应该在测试中引发。 默认为True
。3.0 新功能: 增加了
raise_request_exception
参数。拥有
Client
实例后,您可以调用以下任何方法:- get(path, data=None, follow=False, secure=False, **extra)
对提供的
path
发出 GET 请求并返回Response
对象,如下所述。data
字典中的键值对用于创建 GET 数据负载。 例如:>>> c = Client() >>> c.get('/customers/details/', {'name': 'fred', 'age': 7})
...将导致对 GET 请求的评估相当于:
/customers/details/?name=fred&age=7
extra
关键字参数参数可用于指定要在请求中发送的标头。 例如:>>> c = Client() >>> c.get('/customers/details/', {'name': 'fred', 'age': 7}, ... HTTP_ACCEPT='application/json')
...将 HTTP 标头
HTTP_ACCEPT
发送到详细信息视图,这是测试使用 django.http.HttpRequest.accepts() 方法的代码路径的好方法。CGI规范
通过
**extra
发送的标头应遵循 CGI 规范。 例如,模拟从浏览器到服务器的 HTTP 请求中发送的不同“主机”标头应作为HTTP_HOST
传递。如果您已经有 URL 编码形式的 GET 参数,则可以使用该编码而不是使用数据参数。 例如,之前的 GET 请求也可以是:
>>> c = Client() >>> c.get('/customers/details/?name=fred&age=7')
如果您提供的 URL 包含编码的 GET 数据和数据参数,则数据参数将优先。
如果将
follow
设置为True
,客户端将遵循任何重定向,并且redirect_chain
属性将在包含中间 URL 和状态代码元组的响应对象中设置。如果您有一个重定向到
/next/
的 URL/redirect_me/
,然后重定向到/final/
,这就是您会看到的:>>> response = c.get('/redirect_me/', follow=True) >>> response.redirect_chain [('http://testserver/next/', 302), ('http://testserver/final/', 302)]
如果您将
secure
设置为True
,客户端将模拟 HTTPS 请求。
- post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, **extra)
对提供的
path
发出 POST 请求并返回Response
对象,如下所述。data
字典中的键值对用于提交POST数据。 例如:>>> c = Client() >>> c.post('/login/', {'name': 'fred', 'passwd': 'secret'})
...将导致评估对此 URL 的 POST 请求:
/login/
...使用此 POST 数据:
name=fred&passwd=secret
如果您提供
content_type
作为 application/json,则data
使用json.dumps()
序列化,如果它是一个字典、列表或元组。 默认情况下使用 DjangoJSONEncoder 执行序列化,并且可以通过向 Client 提供json_encoder
参数来覆盖。 这种序列化也适用于 put()、patch() 和 delete() 请求。如果您提供任何其他
content_type
(例如 text/xml 用于 XML 负载),data
的内容在 POST 请求中按原样发送,在 HTTPContent-Type
中使用content_type
标题。如果您不为
content_type
提供值,则data
中的值将以 multipart/form-data 的内容类型传输。 在这种情况下,data
中的键值对将被编码为多部分消息并用于创建 POST 数据负载。要为给定键提交多个值 - 例如,指定
<select multiple>
的选择 - 以列表或元组的形式提供所需键的值。 例如,data
的这个值将为名为choices
的字段提交三个选定的值:{'choices': ('a', 'b', 'd')}
提交文件是一种特殊情况。 要发布文件,您只需要提供文件字段名称作为键,并提供您希望上传的文件的文件句柄作为值。 例如:
>>> c = Client() >>> with open('wishlist.doc') as fp: ... c.post('/customers/wishes/', {'name': 'fred', 'attachment': fp})
(此处的名称
attachment
无关紧要;使用文件处理代码期望的任何名称。)您还可以提供任何类似文件的对象(例如,
StringIO
或BytesIO
)作为文件句柄。 如果您上传到 ImageField,则对象需要一个name
属性,该属性通过 validate_image_file_extension 验证器。 例如:>>> from io import BytesIO >>> img = BytesIO(b'mybinarydata') >>> img.name = 'myimage.jpg'
请注意,如果您希望对多个
post()
调用使用相同的文件句柄,那么您将需要在帖子之间手动重置文件指针。 最简单的方法是在文件提供给post()
后手动关闭文件,如上所示。您还应该确保以允许读取数据的方式打开文件。 如果您的文件包含二进制数据,例如图像,这意味着您需要以
rb
(读取二进制)模式打开文件。extra
参数的作用与 Client.get() 相同。如果您使用 POST 请求的 URL 包含编码参数,则这些参数将在 request.GET 数据中可用。 例如,如果您要提出请求:
>>> c.post('/login/?visitor=true', {'name': 'fred', 'passwd': 'secret'})
... 处理这个请求的视图可以询问 request.POST 来检索用户名和密码,并且可以询问 request.GET 来确定用户是否是访客。
如果将
follow
设置为True
,客户端将遵循任何重定向,并且redirect_chain
属性将在包含中间 URL 和状态代码元组的响应对象中设置。如果您将
secure
设置为True
,客户端将模拟 HTTPS 请求。
- head(path, data=None, follow=False, secure=False, **extra)
对提供的
path
发出 HEAD 请求并返回Response
对象。 此方法的工作方式与 Client.get() 类似,包括follow
、secure
和extra
参数,只是它不返回消息正文。
- options(path, data=, content_type='application/octet-stream', follow=False, secure=False, **extra)
对提供的
path
发出 OPTIONS 请求并返回Response
对象。 用于测试 RESTful 接口。当提供
data
时,将其用作请求正文,并将Content-Type
标头设置为content_type
。follow
、secure
和extra
参数的作用与 Client.get() 相同。
- put(path, data=, content_type='application/octet-stream', follow=False, secure=False, **extra)
对提供的
path
发出 PUT 请求并返回Response
对象。 用于测试 RESTful 接口。当提供
data
时,将其用作请求正文,并将Content-Type
标头设置为content_type
。follow
、secure
和extra
参数的作用与 Client.get() 相同。
- patch(path, data=, content_type='application/octet-stream', follow=False, secure=False, **extra)
对提供的
path
发出 PATCH 请求并返回Response
对象。 用于测试 RESTful 接口。follow
、secure
和extra
参数的作用与 Client.get() 相同。
- delete(path, data=, content_type='application/octet-stream', follow=False, secure=False, **extra)
对提供的
path
发出 DELETE 请求并返回Response
对象。 用于测试 RESTful 接口。当提供
data
时,将其用作请求正文,并将Content-Type
标头设置为content_type
。follow
、secure
和extra
参数的作用与 Client.get() 相同。
- trace(path, follow=False, secure=False, **extra)
对提供的
path
发出 TRACE 请求并返回Response
对象。 用于模拟诊断探针。与其他请求方法不同,为了符合 RFC 7231#section-4.3.8 要求 TRACE 请求,不提供
data
作为关键字参数不能有身体。follow
、secure
和extra
参数的作用与 Client.get() 相同。
- login(**credentials)
如果您的站点使用 Django 的 认证系统 并且您处理用户登录,则可以使用测试客户端的
login()
方法来模拟用户登录站点的效果。调用此方法后,测试客户端将拥有通过任何可能构成视图一部分的基于登录的测试所需的所有 cookie 和会话数据。
credentials
参数的格式取决于您使用的 身份验证后端 (由您的 :setting:`AUTHENTICATION_BACKENDS` 设置配置)。 如果您使用 Django 提供的标准身份验证后端 (ModelBackend
),credentials
应该是用户的用户名和密码,作为关键字参数提供:>>> c = Client() >>> c.login(username='fred', password='secret') # Now you can access a view that's only available to logged-in users.
如果您使用不同的身份验证后端,则此方法可能需要不同的凭据。 它需要后端的
authenticate()
方法所需的任何凭据。login()
如果凭据被接受并且登录成功,则返回True
。最后,您需要记住在使用此方法之前创建用户帐户。 正如我们上面解释的,测试运行器是使用测试数据库执行的,默认情况下不包含用户。 因此,在您的生产站点上有效的用户帐户在测试条件下将不起作用。 您需要创建用户作为测试套件的一部分——手动(使用 Django 模型 API)或使用测试装置。 请记住,如果您希望您的测试用户拥有密码,则不能通过直接设置密码属性来设置用户密码——您必须使用 set_password() 函数来存储正确散列的密码。 或者,您可以使用 create_user() 辅助方法创建一个具有正确散列密码的新用户。
- force_login(user, backend=None)
如果您的站点使用Django的认证系统,您可以使用
force_login()
方法来模拟用户登录站点的效果。 当测试需要用户登录并且用户登录方式的详细信息不重要时,请使用此方法而不是 login()。与
login()
不同,此方法跳过身份验证和验证步骤:允许非活动用户 (is_active=False) 登录,并且不需要提供用户凭据。用户将其
backend
属性设置为backend
参数的值(应该是带点的 Python 路径字符串),如果值不是,则设置为settings.AUTHENTICATION_BACKENDS[0]
假如。 由 login() 调用的 authenticate() 函数通常会像这样注释用户。这种方法比
login()
更快,因为绕过了昂贵的密码散列算法。 此外,您可以在测试 时使用较弱的散列器通过 加速login()
。
- logout()
如果您的站点使用 Django 的 认证系统 ,则可以使用
logout()
方法来模拟用户注销您站点的效果。调用此方法后,测试客户端会将所有 cookie 和会话数据清除为默认值。 后续请求似乎来自 AnonymousUser。
测试响应
get()
和 post()
方法都返回一个 Response
对象。 这个 Response
对象是 不是 与 Django 视图返回的 HttpResponse
对象相同; 测试响应对象有一些额外的数据,可用于测试代码验证。
具体来说,Response
对象具有以下属性:
- class Response
- client
用于发出导致响应的请求的测试客户端。
- content
响应的正文,作为字节串。 这是视图呈现的最终页面内容或任何错误消息。
- context
用于呈现生成响应内容的模板的模板
Context
实例。如果渲染的页面使用了多个模板,那么
context
将是一个Context
对象的列表,按照它们被渲染的顺序。无论渲染期间使用的模板数量如何,您都可以使用
[]
运算符检索上下文值。 例如,可以使用以下方法检索上下文变量name
:>>> response = client.get('/foo/') >>> response.context['name'] 'Arthur'
不使用 Django 模板?
此属性仅在使用 DjangoTemplates 后端时填充。 如果您使用其他模板引擎,context_data 可能是具有该属性的响应的合适替代方案。
- exc_info
3.0 版中的新功能。
一个包含三个值的元组,提供有关在视图期间发生的未处理异常(如果有)的信息。
这些值是 (type, value, traceback),与 Python 的
sys.exc_info()
返回的相同。 它们的含义是:type:异常的类型。
value:异常实例。
traceback:一个回溯对象,它封装了最初发生异常的点的调用堆栈。
如果没有异常发生,那么
exc_info
将是None
。
- json(**kwargs)
响应的正文,解析为 JSON。 额外的关键字参数被传递给
json.loads()
。 例如:>>> response = client.get('/foo/') >>> response.json()['name'] 'Arthur'
如果
Content-Type
标头不是"application/json"
,则在尝试解析响应时将引发ValueError
。
- request
激发响应的请求数据。
- wsgi_request
由生成响应的测试处理程序生成的
WSGIRequest
实例。
- status_code
响应的 HTTP 状态,以整数表示。 有关已定义代码的完整列表,请参阅 IANA 状态代码注册表 。
- templates
用于呈现最终内容的
Template
实例列表,按照它们的呈现顺序。 对于列表中的每个模板,如果模板是从文件加载的,则使用template.name
获取模板的文件名。 (名称是一个字符串,例如'admin/index.html'
。)不使用 Django 模板?
此属性仅在使用 DjangoTemplates 后端时填充。 如果您使用其他模板引擎,如果您只需要用于渲染的模板的名称,则 template_name 可能是一个合适的替代方案。
- resolver_match
响应的 ResolverMatch 实例。 例如,您可以使用 func 属性来验证提供响应的视图:
# my_view here is a function based view self.assertEqual(response.resolver_match.func, my_view) # class-based views need to be compared by name, as the functions # generated by as_view() won't be equal self.assertEqual(response.resolver_match.func.__name__, MyView.as_view().__name__)
如果未找到给定的 URL,访问此属性将引发 Resolver404 异常。
您还可以在响应对象上使用字典语法来查询 HTTP 标头中任何设置的值。 例如,您可以使用 response['Content-Type']
确定响应的内容类型。
例外
如果您将测试客户端指向引发异常的视图,并且 Client.raise_request_exception
为 True
,则该异常将在测试用例中可见。 然后,您可以使用标准的 try ... except
块或 assertRaises()
来测试异常。
测试客户端不可见的唯一例外是 Http404、PermissionDenied、SystemExit
和 SuspiciousOperation。 Django 在内部捕获这些异常并将它们转换为适当的 HTTP 响应代码。 在这些情况下,您可以在测试中检查 response.status_code
。
如果 Client.raise_request_exception
是 False
,测试客户端将返回 500 响应,就像返回给浏览器一样。 响应具有属性 exc_info 以提供有关未处理异常的信息。
持久状态
测试客户端是有状态的。 如果响应返回 cookie,则该 cookie 将存储在测试客户端中,并与所有后续 get()
和 post()
请求一起发送。
不遵循这些 cookie 的过期政策。 如果您希望 cookie 过期,请手动删除它或创建一个新的 Client
实例(这将有效地删除所有 cookie)。
测试客户端有两个存储持久状态信息的属性。 您可以访问这些属性作为测试条件的一部分。
- Client.cookies
- 一个 Python
SimpleCookie
对象,包含所有客户端 cookie 的当前值。 有关更多信息,请参阅http.cookies
模块的文档。
- Client.session
包含会话信息的类似字典的对象。 有关完整详细信息,请参阅 会话文档 。
要修改会话然后保存它,必须先将它存储在一个变量中(因为每次访问该属性时都会创建一个新的
SessionStore
):def test_something(self): session = self.client.session session['somekey'] = 'test' session.save()
设置语言
在测试支持国际化和本地化的应用程序时,您可能希望为测试客户端请求设置语言。 这样做的方法取决于是否启用了 LocaleMiddleware。
如果启用了中间件,则可以通过创建名称为 :setting:`LANGUAGE_COOKIE_NAME` 和语言代码值的 cookie 来设置语言:
from django.conf import settings
def test_language_using_cookie(self):
self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: 'fr'})
response = self.client.get('/')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
或者通过在请求中包含 Accept-Language
HTTP 标头:
def test_language_using_header(self):
response = self.client.get('/', HTTP_ACCEPT_LANGUAGE='fr')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
更多细节在 如何 Django 发现语言偏好 。
如果未启用中间件,则可以使用 translation.override() 设置活动语言:
from django.utils import translation
def test_language_using_override(self):
with translation.override('fr'):
response = self.client.get('/')
self.assertEqual(response.content, b"Bienvenue sur mon site.")
更多细节在显式设置活动语言。
示例
以下是使用测试客户端的单元测试:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
# Every test needs a client.
self.client = Client()
def test_details(self):
# Issue a GET request.
response = self.client.get('/customer/details/')
# Check that the response is 200 OK.
self.assertEqual(response.status_code, 200)
# Check that the rendered context contains 5 customers.
self.assertEqual(len(response.context['customers']), 5)
提供的测试用例类
普通 Python 单元测试类扩展了 unittest.TestCase
的基类。 Django 提供了这个基类的一些扩展:
[[../File:../../_images/django_unittest_classes_hierarchy|thumb|none|508x328px|alt=
| ]]您可以将普通的 unittest.TestCase
转换为任何子类:将测试的基类从 unittest.TestCase
更改为子类。 所有标准 Python 单元测试功能都将可用,并且将增加一些有用的附加功能,如下文各部分所述。
SimpleTestCase
- class SimpleTestCase
添加此功能的 unittest.TestCase
的子类:
如果您的测试进行任何数据库查询,请使用子类 TransactionTestCase 或 TestCase。
- SimpleTestCase.databases
- SimpleTestCase 默认不允许数据库查询。 这有助于避免执行会影响其他测试的写入查询,因为每个
SimpleTestCase
测试都不是在事务中运行的。 如果您不关心这个问题,您可以通过在测试类上将databases
类属性设置为'__all__'
来禁用此行为。
警告
SimpleTestCase
及其子类(例如 TestCase
, ...) 依赖 setUpClass()
和 tearDownClass()
来执行一些类范围的初始化(例如 覆盖设置)。 如果您需要覆盖这些方法,请不要忘记调用 super
实现:
class MyTestCase(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
...
@classmethod
def tearDownClass(cls):
...
super().tearDownClass()
如果在 setUpClass()
期间引发异常,请务必考虑 Python 的行为。 如果发生这种情况,类中的测试和 tearDownClass()
都不会运行。 在 django.test.TestCase 的情况下,这将泄漏在 super()
中创建的事务,这会导致各种症状,包括某些平台上的分段错误(在 macOS 上报告)。 如果你想在 setUpClass()
中故意引发一个异常,例如 unittest.SkipTest
,请务必在调用 super()
之前执行以避免这种情况。
3.1 版更改: 实现了 debug()
方法,以允许在不收集结果和捕获异常的情况下运行测试。
TransactionTestCase
- class TransactionTestCase
TransactionTestCase
继承自 SimpleTestCase 以添加一些特定于数据库的功能:
Django 的 TestCase 类是 TransactionTestCase
的一个更常用的子类,它利用数据库事务设施来加速在每次测试开始时将数据库重置为已知状态的过程。 然而,这样做的结果是无法在 Django TestCase
类中测试某些数据库行为。 例如,您不能像使用 select_for_update() 时所要求的那样测试代码块是否在事务中执行。 在这些情况下,您应该使用 TransactionTestCase
。
TransactionTestCase
和 TestCase
除了将数据库重置为已知状态的方式以及测试代码测试提交和回滚效果的能力外,是相同的:
- A
TransactionTestCase
在测试运行后通过截断所有表来重置数据库。TransactionTestCase
可以调用 commit 和 rollback 并观察这些调用对数据库的影响。 - 另一方面,
TestCase
不会在测试后截断表。 相反,它将测试代码包含在一个在测试结束时回滚的数据库事务中。 这保证了测试结束时的回滚将数据库恢复到其初始状态。
警告
TestCase
在不支持回滚的数据库上运行(例如 带有 MyISAM 存储引擎的 MySQL) 和 TransactionTestCase
的所有实例,将在测试结束时通过从测试数据库中删除所有数据来回滚。
应用程序 不会看到其数据重新加载 ; 如果您需要此功能(例如,第三方应用程序应启用此功能),您可以在 TestCase
主体内设置 serialized_rollback = True
。
TestCase
- class TestCase
这是在 Django 中编写测试最常用的类。 它继承自 TransactionTestCase(并通过扩展 SimpleTestCase)。 如果您的 Django 应用程序不使用数据库,请使用 SimpleTestCase。
班级:
- 将测试包装在两个嵌套的 atomic() 块中:一个用于整个类,一个用于每个测试。 因此,如果要测试某些特定的数据库事务行为,请使用 TransactionTestCase。
- 在每个测试结束时检查可延迟的数据库约束。
它还提供了一个额外的方法:
- classmethod TestCase.setUpTestData()
上面描述的类级别
atomic
块允许在类级别创建初始数据,一次用于整个TestCase
。 与使用setUp()
相比,此技术允许进行更快的测试。例如:
from django.test import TestCase class MyTests(TestCase): @classmethod def setUpTestData(cls): # Set up data for the whole TestCase cls.foo = Foo.objects.create(bar="Test") ... def test1(self): # Some test using self.foo ... def test2(self): # Some other test using self.foo ...
请注意,如果测试在没有事务支持的数据库上运行(例如,带有 MyISAM 引擎的 MySQL),则在每次测试之前都会调用
setUpTestData()
,从而抵消速度优势。注意不要在测试方法中修改在
setUpTestData()
中创建的任何对象。 在类级别完成的设置工作对内存中对象的修改将在测试方法之间持续存在。 例如,如果您确实需要修改它们,则可以使用 refresh_from_db() 在setUp()
方法中重新加载它们。
LiveServerTestCase
- class LiveServerTestCase
LiveServerTestCase
与 TransactionTestCase 基本相同,但有一个额外的功能:它在设置时在后台启动一个实时的 Django 服务器,并在拆卸时关闭它。 这允许使用除 Django 虚拟客户端 以外的自动化测试客户端,例如 Selenium 客户端,在浏览器中执行一系列功能测试并模拟真实的用户的操作。
实时服务器侦听 localhost
并绑定到端口 0,该端口使用操作系统分配的空闲端口。 在测试期间可以使用 self.live_server_url
访问服务器的 URL。
为了演示如何使用 LiveServerTestCase
,让我们编写一个 Selenium 测试。 首先,您需要将 selenium 包 安装到您的 Python 路径中:
然后,将基于 LiveServerTestCase
的测试添加到您应用的测试模块(例如:myapp/tests.py
)。 在本例中,我们假设您使用的是 staticfiles 应用程序,并希望在执行测试期间提供静态文件,类似于我们在开发时使用 DEBUG=True
获得的内容, IE 无需使用 :djadmin:`collectstatic` 收集它们。 我们将使用提供该功能的 StaticLiveServerTestCase 子类。 如果不需要,请将其替换为 django.test.LiveServerTestCase
。
此测试的代码可能如下所示:
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium.webdriver.firefox.webdriver import WebDriver
class MySeleniumTests(StaticLiveServerTestCase):
fixtures = ['user-data.json']
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.selenium = WebDriver()
cls.selenium.implicitly_wait(10)
@classmethod
def tearDownClass(cls):
cls.selenium.quit()
super().tearDownClass()
def test_login(self):
self.selenium.get('%s%s' % (self.live_server_url, '/login/'))
username_input = self.selenium.find_element_by_name("username")
username_input.send_keys('myuser')
password_input = self.selenium.find_element_by_name("password")
password_input.send_keys('secret')
self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()
最后,您可以按如下方式运行测试:
此示例将自动打开 Firefox,然后转到登录页面,输入凭据并按“登录”按钮。 Selenium 提供了其他驱动程序,以防您没有安装 Firefox 或希望使用其他浏览器。 上面的例子只是 Selenium 客户端可以做的一小部分; 查看 完整参考 了解更多详情。
笔记
当使用内存中的 SQLite 数据库运行测试时,同一个数据库连接将由两个并行线程共享:运行实时服务器的线程和运行测试用例的线程。 防止两个线程通过此共享连接同时进行数据库查询很重要,因为这有时可能会随机导致测试失败。 所以需要保证两个线程不会同时访问数据库。 特别是,这意味着在某些情况下(例如,就在单击链接或提交表单之后),您可能需要检查 Selenium 是否收到了响应以及是否加载了下一页,然后再继续执行进一步的测试。 例如,通过让 Selenium 等待直到在响应中找到 <body>
HTML 标记(需要 Selenium > 2.13)来执行此操作:
def test_login(self):
from selenium.webdriver.support.wait import WebDriverWait
timeout = 2
...
self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()
# Wait until the response is received
WebDriverWait(self.selenium, timeout).until(
lambda driver: driver.find_element_by_tag_name('body'))
这里的棘手之处在于,实际上没有“页面加载”这样的东西,尤其是在服务器生成初始文档后动态生成 HTML 的现代 Web 应用程序中。 因此,检查响应中是否存在 <body>
可能不一定适合所有用例。 有关更多信息,请参阅 Selenium FAQ 和 Selenium 文档 。
测试用例功能
默认测试客户端
- SimpleTestCase.client
django.test.*TestCase
实例中的每个测试用例都可以访问 Django 测试客户端的实例。 此客户端可以作为 self.client
访问。 为每个测试重新创建此客户端,因此您不必担心状态(例如 cookie)从一个测试转移到另一个测试。
这意味着,而不是在每个测试中实例化 Client
:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def test_details(self):
client = Client()
response = client.get('/customer/details/')
self.assertEqual(response.status_code, 200)
def test_index(self):
client = Client()
response = client.get('/customer/index/')
self.assertEqual(response.status_code, 200)
...你可以参考 self.client
,如下所示:
from django.test import TestCase
class SimpleTest(TestCase):
def test_details(self):
response = self.client.get('/customer/details/')
self.assertEqual(response.status_code, 200)
def test_index(self):
response = self.client.get('/customer/index/')
self.assertEqual(response.status_code, 200)
自定义测试客户端
- SimpleTestCase.client_class
如果要使用不同的 Client
类(例如,具有自定义行为的子类),请使用 client_class 类属性:
from django.test import Client, TestCase
class MyTestClient(Client):
# Specialized methods for your environment
...
class MyTest(TestCase):
client_class = MyTestClient
def test_my_stuff(self):
# Here self.client is an instance of MyTestClient...
call_some_test_code()
夹具加载
- TransactionTestCase.fixtures
如果数据库中没有任何数据,那么数据库支持网站的测试用例就没有多大用处。 测试更具可读性,并且使用 ORM 创建对象更易于维护,例如在 TestCase.setUpTestData() 中,但是,您也可以使用夹具。
夹具是 Django 知道如何导入数据库的数据集合。 例如,如果您的站点有用户帐户,您可能会设置一个假用户帐户的固定装置,以便在测试期间填充您的数据库。
创建夹具最直接的方法是使用 :djadmin:`manage.py 转储数据 ` 命令。 这假设您的数据库中已经有一些数据。 见 :djadmin:`dumpdata 文档 ` 更多细节。
一旦您创建了一个装置并将其放置在 :setting:`INSTALLED_APPS` 之一的 fixtures
目录中,您可以通过指定 fixtures
类属性:
from django.test import TestCase
from myapp.models import Animal
class AnimalTestCase(TestCase):
fixtures = ['mammals.json', 'birds']
def setUp(self):
# Test definitions as before.
call_setup_methods()
def test_fluffy_animals(self):
# A test that uses the fixtures.
call_some_test_code()
以下是具体会发生的事情:
- 在每次测试开始时,在运行
setUp()
之前,Django 会刷新数据库,将数据库直接返回到调用 :djadmin:`migrate` 后的状态。 - 然后,安装所有命名的装置。 在这个例子中,Django 将安装任何名为
mammals
的 JSON 夹具,然后是任何名为birds
的夹具。 请参阅 :djadmin:`loaddata` 文档以获取有关定义和安装设备的更多详细信息。
出于性能原因,TestCase 在 setUpTestData() 之前为整个测试类加载一次设备,而不是在每次测试之前,并且在每次测试之前使用事务来清理数据库。 在任何情况下,您都可以确定测试的结果不会受到另一个测试或测试执行顺序的影响。
默认情况下,灯具仅加载到 default
数据库中。 如果您使用多个数据库并设置 TransactionTestCase.databases,则夹具将加载到所有指定的数据库中。
URLconf 配置
如果您的应用程序提供视图,您可能希望包含使用测试客户端来执行这些视图的测试。 但是,最终用户可以在他们选择的任何 URL 上自由地在您的应用程序中部署视图。 这意味着您的测试不能依赖于您的视图将在特定 URL 上可用的事实。 使用 @override_settings(ROOT_URLCONF=...)
为 URLconf 配置装饰您的测试类或测试方法。
多数据库支持
- TransactionTestCase.databases
Django 设置了一个测试数据库,该数据库对应于您设置中的 :setting:`DATABASES` 定义中定义的每个数据库,并通过 databases
至少被一个测试引用。
然而,运行 Django TestCase
所花费的大部分时间都被调用 flush
消耗了,这确保您在每次测试运行开始时都有一个干净的数据库。 如果您有多个数据库,则需要多次刷新(每个数据库一次),这可能是一项耗时的活动——尤其是当您的测试不需要测试多数据库活动时。
作为优化,Django 仅在每次测试运行开始时刷新 default
数据库。 如果您的设置包含多个数据库,并且您的测试要求清理每个数据库,则可以使用测试套件上的 databases
属性来请求刷新额外的数据库。
例如:
class TestMyViews(TransactionTestCase):
databases = {'default', 'other'}
def test_index_page_view(self):
call_some_test_code()
此测试用例将在运行 test_index_page_view
之前刷新 default
和 other
测试数据库。 您还可以使用 '__all__'
指定必须刷新所有测试数据库。
databases
标志还控制将 TransactionTestCase.fixtures 加载到哪些数据库中。 默认情况下,灯具仅加载到 default
数据库中。
对不在 databases
中的数据库的查询将给出断言错误,以防止测试之间的状态泄漏。
- TestCase.databases
默认情况下,在 TestCase
的执行期间,只有 default
数据库会被包装在一个事务中,并且尝试查询其他数据库将导致断言错误,以防止测试之间的状态泄漏。
使用测试类上的 databases
类属性请求针对非 default
数据库的事务包装。
例如:
class OtherDBTests(TestCase):
databases = {'other'}
def test_other_db_query(self):
...
此测试将仅允许对 other
数据库进行查询。 就像 SimpleTestCase.databases 和 TransactionTestCase.databases 一样,'__all__'
常量可用于指定测试应允许对所有数据库进行查询。
覆盖设置
警告
使用以下功能临时更改测试中的设置值。 不要直接操作 django.conf.settings
,因为这样操作后 Django 不会恢复原始值。
- SimpleTestCase.settings()
出于测试目的,临时更改设置并在运行测试代码后恢复到原始值通常很有用。 对于这个用例,Django 提供了一个名为 settings() 的标准 Python 上下文管理器(参见 PEP 343),可以像这样使用:
from django.test import TestCase
class LoginTestCase(TestCase):
def test_login(self):
# First check for the default behavior
response = self.client.get('/sekrit/')
self.assertRedirects(response, '/accounts/login/?next=/sekrit/')
# Then override the LOGIN_URL setting
with self.settings(LOGIN_URL='/other/login/'):
response = self.client.get('/sekrit/')
self.assertRedirects(response, '/other/login/?next=/sekrit/')
此示例将覆盖 with
块中代码的 :setting:`LOGIN_URL` 设置,然后将其值重置为之前的状态。
- SimpleTestCase.modify_settings()
重新定义包含值列表的设置可能很麻烦。 在实践中,添加或删除值通常就足够了。 Django 提供了 modify_settings() 上下文管理器,以便于更改设置:
from django.test import TestCase
class MiddlewareTestCase(TestCase):
def test_cache_middleware(self):
with self.modify_settings(MIDDLEWARE={
'append': 'django.middleware.cache.FetchFromCacheMiddleware',
'prepend': 'django.middleware.cache.UpdateCacheMiddleware',
'remove': [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
],
}):
response = self.client.get('/')
# ...
对于每个操作,您可以提供值列表或字符串。 当列表中已经存在该值时,append
和prepend
无效; 当值不存在时 remove
也不会。
- override_settings()
如果您想覆盖测试方法的设置,Django 提供了 override_settings() 装饰器(参见 PEP 318)。 它是这样使用的:
from django.test import TestCase, override_settings
class LoginTestCase(TestCase):
@override_settings(LOGIN_URL='/other/login/')
def test_login(self):
response = self.client.get('/sekrit/')
self.assertRedirects(response, '/other/login/?next=/sekrit/')
装饰器也可以应用于 TestCase 类:
from django.test import TestCase, override_settings
@override_settings(LOGIN_URL='/other/login/')
class LoginTestCase(TestCase):
def test_login(self):
response = self.client.get('/sekrit/')
self.assertRedirects(response, '/other/login/?next=/sekrit/')
- modify_settings()
同样,Django 提供了 modify_settings() 装饰器:
from django.test import TestCase, modify_settings
class MiddlewareTestCase(TestCase):
@modify_settings(MIDDLEWARE={
'append': 'django.middleware.cache.FetchFromCacheMiddleware',
'prepend': 'django.middleware.cache.UpdateCacheMiddleware',
})
def test_cache_middleware(self):
response = self.client.get('/')
# ...
装饰器也可以应用于测试用例类:
from django.test import TestCase, modify_settings
@modify_settings(MIDDLEWARE={
'append': 'django.middleware.cache.FetchFromCacheMiddleware',
'prepend': 'django.middleware.cache.UpdateCacheMiddleware',
})
class MiddlewareTestCase(TestCase):
def test_cache_middleware(self):
response = self.client.get('/')
# ...
笔记
当给定一个类时,这些装饰器直接修改该类并返回它; 他们不会创建并返回它的修改副本。 因此,如果您尝试调整上述示例以将返回值分配给与 LoginTestCase
或 MiddlewareTestCase
不同的名称,您可能会惊讶地发现原始测试用例类仍然受到相同的影响装饰者。 对于给定的类,modify_settings() 总是在 override_settings() 之后应用。
警告
设置文件包含一些仅在 Django 内部初始化期间参考的设置。 如果你用 override_settings
改变它们,如果你通过 django.conf.settings
模块访问它,设置也会改变,但是,Django 的内部访问它的方式不同。 实际上,将 override_settings() 或 modify_settings() 与这些设置一起使用可能不会达到您期望的效果。
我们不建议更改 :setting:`DATABASES` 设置。 改变 :setting:`CACHES` 设置是可能的,但如果你使用使用缓存的内部结构,比如 django.contrib.sessions,则有点棘手。 例如,您必须在使用缓存会话并覆盖 :setting:`CACHES` 的测试中重新初始化会话后端。
最后,避免将您的设置别名为模块级常量,因为 override_settings()
不适用于此类值,因为它们仅在第一次导入模块时进行评估。
您还可以通过在设置被覆盖后删除设置来模拟不存在设置,如下所示:
@override_settings()
def test_something(self):
del settings.LOGIN_URL
...
覆盖设置时,请确保处理应用代码使用缓存或类似功能的情况,即使设置更改也保留状态。 Django 提供 django.test.signals.setting_changed 信号,允许您注册回调以在设置更改时清除或重置状态。
Django 本身使用这个信号来重置各种数据:
覆盖设置 | 数据重置 |
---|---|
USE_TZ, TIME_ZONE | 数据库时区 |
模板 | 模板引擎 |
SERIALIZATION_MODULES | 序列化器缓存 |
LOCALE_PATHS, LANGUAGE_CODE | 默认翻译和加载的翻译 |
MEDIA_ROOT、DEFAULT_FILE_STORAGE | 默认文件存储 |
清空测试发件箱
如果您使用任何 Django 的自定义 TestCase
类,测试运行程序将在每个测试用例开始时清除测试电子邮件发件箱的内容。
有关测试期间电子邮件服务的更多详细信息,请参阅下面的 电子邮件服务 。
断言
由于 Python 的普通 unittest.TestCase
类实现了 assertTrue()
和 assertEqual()
等断言方法,Django 的自定义 TestCase 类提供了许多自定义断言方法,这些方法对于测试 Web 应用程序:
大多数这些断言方法给出的失败消息可以使用 msg_prefix
参数进行自定义。 此字符串将作为断言生成的任何失败消息的前缀。 这允许您提供其他详细信息,以帮助您确定测试套件中失败的位置和原因。
- SimpleTestCase.assertRaisesMessage(expected_exception, expected_message, callable, *args, **kwargs)
SimpleTestCase.assertRaisesMessage(expected_exception, expected_message) 断言
callable
的执行引发expected_exception
并且在异常消息中找到expected_message
。 任何其他结果都报告为失败。 它是unittest.TestCase.assertRaisesRegex()
的更简单版本,区别在于expected_message
不被视为正则表达式。如果只给出了
expected_exception
和expected_message
参数,则返回一个上下文管理器,以便被测试的代码可以内联而不是作为函数编写:with self.assertRaisesMessage(ValueError, 'invalid literal for int()'): int('a')
- SimpleTestCase.assertWarnsMessage(expected_warning, expected_message, callable, *args, **kwargs)
SimpleTestCase.assertWarnsMessage(expected_warning, expected_message)
- 类似于 SimpleTestCase.assertRaisesMessage() 但用于
assertWarnsRegex()
而不是assertRaisesRegex()
。
- SimpleTestCase.assertFieldOutput(fieldclass, valid, invalid, field_args=None, field_kwargs=None, empty_value=)
断言表单字段对各种输入的行为正确。
- 参数
fieldclass – 要测试的字段的类。
valid – 一个字典,将有效输入映射到其预期的清理值。
invalid – 将无效输入映射到一个或多个引发的错误消息的字典。
field_args – 传递给实例化字段的参数。
field_kwargs – 传递给实例化字段的 kwargs。
empty_value –
empty_values
中输入的预期干净输出。
例如,以下代码测试
EmailField
接受a@a.com
作为有效电子邮件地址,但拒绝aaa
并显示合理的错误消息:self.assertFieldOutput(EmailField, {'a@a.com': 'a@a.com'}, {'aaa': ['Enter a valid email address.']})
- SimpleTestCase.assertFormError(response, form, field, errors, msg_prefix=)
断言表单上的字段在呈现在表单上时会引发提供的错误列表。
form
是Form
实例在模板上下文中的名称。field
是表单上要检查的字段的名称。 如果field
的值为None
,将检查非字段错误(您可以通过 form.non_field_errors() 访问的错误)。errors
是错误字符串或错误字符串列表,预期作为表单验证的结果。
- SimpleTestCase.assertFormsetError(response, formset, form_index, field, errors, msg_prefix=)
断言
formset
在呈现时引发提供的错误列表。formset
是Formset
实例在模板上下文中的名称。form_index
是Formset
中表格的编号。 如果form_index
的值为None
,将检查非形式错误(您可以通过formset.non_form_errors()
访问的错误)。field
是表单上要检查的字段的名称。 如果field
的值为None
,将检查非字段错误(您可以通过 form.non_field_errors() 访问的错误)。errors
是错误字符串或错误字符串列表,预期作为表单验证的结果。
- SimpleTestCase.assertContains(response, text, count=None, status_code=200, msg_prefix=, html=False)
断言
Response
实例产生给定的status_code
并且text
出现在响应的内容中。 如果提供了count
,则响应中的text
必须恰好出现count
次。将
html
设置为True
以将text
处理为 HTML。 与响应内容的比较将基于 HTML 语义而不是逐个字符的相等性。 在大多数情况下,空格会被忽略,属性排序并不重要。 有关更多详细信息,请参阅 assertHTMLEqual()。
- SimpleTestCase.assertNotContains(response, text, status_code=200, msg_prefix=, html=False)
断言
Response
实例产生了给定的status_code
并且text
没有 没有 出现在响应的内容中。将
html
设置为True
以将text
处理为 HTML。 与响应内容的比较将基于 HTML 语义而不是逐个字符的相等性。 在大多数情况下,空格会被忽略,属性排序并不重要。 有关更多详细信息,请参阅 assertHTMLEqual()。
- SimpleTestCase.assertTemplateUsed(response, template_name, msg_prefix=, count=None)
断言具有给定名称的模板用于呈现响应。
名称是一个字符串,例如
'admin/index.html'
。count 参数是一个整数,指示应呈现模板的次数。 默认为
None
,这意味着模板应该被渲染一次或多次。您可以将其用作上下文管理器,如下所示:
with self.assertTemplateUsed('index.html'): render_to_string('index.html') with self.assertTemplateUsed(template_name='index.html'): render_to_string('index.html')
- SimpleTestCase.assertTemplateNotUsed(response, template_name, msg_prefix=)
断言具有给定名称的模板是 not 用于呈现响应。
您可以使用与 assertTemplateUsed() 相同的方式将其用作上下文管理器。
- SimpleTestCase.assertURLEqual(url1, url2, msg_prefix=)
- 断言两个 URL 相同,忽略除同名参数外的查询字符串参数的顺序。 例如,
/path/?x=1&y=2
等于/path/?y=2&x=1
,但/path/?a=1&a=2
不等于/path/?a=2&a=1
。
- SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix=, fetch_redirect_response=True)
断言响应返回了
status_code
重定向状态,重定向到expected_url
(包括任何GET
数据),并且最终页面接收到target_status_code
。如果您的请求使用了
follow
参数,则expected_url
和target_status_code
将是重定向链终点的 url 和状态代码。如果
fetch_redirect_response
是False
,则不会加载最终页面。 由于测试客户端无法获取外部 URL,这在expected_url
不是您的 Django 应用程序的一部分时特别有用。在两个 URL 之间进行比较时,Scheme 处理正确。 如果在我们重定向到的位置中没有指定任何方案,则使用原始请求的方案。 如果存在,
expected_url
中的方案是用来进行比较的方案。
- SimpleTestCase.assertHTMLEqual(html1, html2, msg=None)
断言字符串
html1
和html2
相等。 比较基于 HTML 语义。 比较考虑到以下几点:HTML 标签前后的空白被忽略。
所有类型的空格都被认为是等效的。
所有打开的标签都隐式关闭,例如 当周围的标签关闭或 HTML 文档结束时。
空标签相当于它们的自闭合版本。
HTML 元素的属性顺序并不重要。
没有参数的属性等于名称和值相等的属性(参见示例)。
引用相同字符的文本、字符引用和实体引用是等效的。
以下示例是有效的测试,不会引发任何
AssertionError
:self.assertHTMLEqual( '<p>Hello <b>'world'!</p>', '''<p> Hello <b>'world'! </b> </p>''' ) self.assertHTMLEqual( '<input type="checkbox" checked="checked" id="id_accept_terms" />', '<input id="id_accept_terms" type="checkbox" checked>' )
html1
和html2
必须是有效的 HTML。 如果其中之一无法解析,则会引发AssertionError
。可以使用
msg
参数自定义出错时的输出。
- SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None)
断言字符串
html1
和html2
不 相等。 比较基于 HTML 语义。 有关详细信息,请参阅 assertHTMLEqual()。html1
和html2
必须是有效的 HTML。 如果其中之一无法解析,则会引发AssertionError
。可以使用
msg
参数自定义出错时的输出。
- SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None)
断言字符串
xml1
和xml2
相等。 比较基于 XML 语义。 与 assertHTMLEqual() 类似,比较是在解析的内容上进行的,因此只考虑语义差异,而不是语法差异。 当在任何参数中传递无效的 XML 时,总是会引发AssertionError
,即使两个字符串相同。XML 声明、文档类型、处理指令和注释将被忽略。 只比较根元素及其子元素。
可以使用
msg
参数自定义出错时的输出。
- SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None)
断言字符串
xml1
和xml2
不 相等。 比较基于 XML 语义。 有关详细信息,请参阅 assertXMLEqual()。可以使用
msg
参数自定义出错时的输出。
- SimpleTestCase.assertInHTML(needle, haystack, count=None, msg_prefix=)
断言 HTML 片段
needle
包含在haystack
片段中。如果指定了
count
整数参数,则另外会严格验证needle
出现的次数。在大多数情况下,空格会被忽略,并且属性排序并不重要。 传入的参数必须是有效的 HTML。
- SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None)
断言 JSON 片段
raw
和expected_data
相等。 通常的 JSON 非重要空白规则适用,因为重量级被委托给json
库。可以使用
msg
参数自定义出错时的输出。
- SimpleTestCase.assertJSONNotEqual(raw, expected_data, msg=None)
断言 JSON 片段
raw
和expected_data
不 相等。 有关更多详细信息,请参阅 assertJSONEqual()。可以使用
msg
参数自定义出错时的输出。
- TransactionTestCase.assertQuerysetEqual(qs, values, transform=repr, ordered=True, msg=None)
断言查询集
qs
返回特定的值列表values
。qs
和values
的内容的比较是通过将transform
应用于qs
来进行的。 默认情况下,这意味着将qs
中每个值的repr()
与values
进行比较。 如果repr()
不提供唯一或有用的比较,则可以使用任何其他可调用对象。默认情况下,比较也是顺序相关的。 如果
qs
不提供隐式排序,您可以将ordered
参数设置为False
,将比较转换为collections.Counter
比较。 如果顺序未定义(如果给定的qs
未排序并且比较针对多个有序值),则会引发ValueError
。可以使用
msg
参数自定义出错时的输出。
- TransactionTestCase.assertNumQueries(num, func, *args, **kwargs)
断言当使用
*args
和**kwargs
调用func
时,会执行num
数据库查询。如果
"using"
键存在于kwargs
中,它被用作检查查询数量的数据库别名:self.assertNumQueries(7, using='non_default_db')
如果你想用
using
参数调用一个函数,你可以通过用lambda
包装调用来添加一个额外的参数来实现:self.assertNumQueries(7, lambda: my_function(using=7))
您还可以将其用作上下文管理器:
with self.assertNumQueries(2): Person.objects.create(name="Aaron") Person.objects.create(name="Daniel")
标记测试
您可以标记测试,以便轻松运行特定子集。 例如,您可以标记快速或慢速测试:
from django.test import tag
class SampleTestCase(TestCase):
@tag('fast')
def test_fast(self):
...
@tag('slow')
def test_slow(self):
...
@tag('slow', 'core')
def test_slow_but_core(self):
...
您还可以标记测试用例:
@tag('slow', 'core')
class SampleTestCase(TestCase):
...
子类从超类继承标签,方法从它们的类继承标签。 鉴于:
@tag('foo')
class SampleTestCaseChild(SampleTestCase):
@tag('bar')
def test(self):
...
SampleTestCaseChild.test
将标记为 'slow'
、'core'
、'bar'
和 'foo'
。
然后您可以选择要运行的测试。 例如,只运行快速测试:
或者运行快速测试和核心测试(即使它很慢):
您还可以按标签排除测试。 要运行核心测试(如果它们不慢):
test --exclude-tag
优先于 test --tag
,因此如果测试有两个标签,并且您选择其中一个并排除另一个,则不会运行该测试。
测试异步代码
3.1 版中的新功能。
如果您只想测试异步视图的输出,标准测试客户端将在它们自己的异步循环中运行它们,而无需您做任何额外的工作。
但是,如果您想为 Django 项目编写完全异步的测试,则需要考虑几件事。
首先,您的测试必须是测试类上的 async def
方法(以便为它们提供异步上下文)。 Django 将自动检测任何 async def
测试并将它们包装起来,以便它们在自己的事件循环中运行。
如果从异步函数进行测试,则还必须使用异步测试客户端。 这在任何测试中都可用作 django.test.AsyncClient
或 self.async_client
。
AsyncClient
与同步(普通)测试客户端具有相同的方法和签名,但有两个例外:
不支持
follow
参数。作为
extra
关键字参数传递的标头不应具有同步客户端所需的HTTP_
前缀(请参阅 Client.get())。 例如,这里是如何设置 HTTPAccept
标头:>>> c = AsyncClient() >>> c.get( ... '/customers/details/', ... {'name': 'fred', 'age': 7}, ... ACCEPT='application/json' ... )
使用 AsyncClient
必须等待任何发出请求的方法:
async def test_my_thing(self):
response = await self.async_client.get('/some-url/')
self.assertEqual(response.status_code, 200)
异步客户端也可以调用同步视图; 它贯穿Django的异步请求路径,两者都支持。 通过 AsyncClient
调用的任何视图都将为其 request
获得一个 ASGIRequest
对象,而不是普通客户端创建的 WSGIRequest
。
警告
如果您使用测试装饰器,它们必须是异步兼容的,以确保它们正常工作。 Django 的内置装饰器将正确运行,但第三方装饰器可能无法执行(它们将“包装”执行流程的错误部分而不是您的测试)。
如果您需要使用这些装饰器,那么您应该使用 async_to_sync() inside 来装饰您的测试方法:
from asgiref.sync import async_to_sync
from django.test import TestCase
class MyTests(TestCase):
@mock.patch(...)
@async_to_sync
async def test_my_thing(self):
...
电子邮件服务
如果您的任何 Django 视图使用 Django 的电子邮件功能 发送电子邮件,您可能不希望每次使用该视图运行测试时都发送电子邮件。 出于这个原因,Django 的测试运行器会自动将所有 Django 发送的电子邮件重定向到一个虚拟发件箱。 这使您可以测试发送电子邮件的各个方面——从发送的消息数量到每条消息的内容——而无需实际发送消息。
测试运行器通过用测试后端透明地替换普通电子邮件后端来实现这一点。 (别担心——这对 Django 之外的任何其他电子邮件发件人没有影响,比如你机器的邮件服务器,如果你正在运行的话。)
- django.core.mail.outbox
在测试运行过程中,每封外发邮件都保存在django.core.mail.outbox
中。 这是已发送的所有 EmailMessage 实例的列表。 outbox
属性是一个特殊的属性,在使用 locmem
电子邮件后端时创建 only。 它通常不作为 django.core.mail 模块的一部分存在,您不能直接导入它。 下面的代码显示了如何正确访问此属性。
这是一个检查 django.core.mail.outbox
的长度和内容的示例测试:
from django.core import mail
from django.test import TestCase
class EmailTest(TestCase):
def test_send_email(self):
# Send message.
mail.send_mail(
'Subject here', 'Here is the message.',
'from@example.com', ['to@example.com'],
fail_silently=False,
)
# Test that one message has been sent.
self.assertEqual(len(mail.outbox), 1)
# Verify that the subject of the first message is correct.
self.assertEqual(mail.outbox[0].subject, 'Subject here')
如前所述 previous ,在 Django *TestCase
中的每个测试开始时,测试发件箱都会被清空。 要手动清空发件箱,请将空列表分配给 mail.outbox
:
from django.core import mail
# Empty the test outbox
mail.outbox = []
管理命令
可以使用 call_command() 函数测试管理命令。 输出可以重定向到 StringIO
实例:
from io import StringIO
from django.core.management import call_command
from django.test import TestCase
class ClosepollTest(TestCase):
def test_command_output(self):
out = StringIO()
call_command('closepoll', stdout=out)
self.assertIn('Expected output', out.getvalue())
跳过测试
unittest 库提供了 @skipIf
和 @skipUnless
装饰器,如果您提前知道这些测试将在某些条件下失败,则允许您跳过测试。
例如,如果您的测试需要特定的可选库才能成功,您可以使用 @skipIf
装饰测试用例。 然后,测试运行器将报告测试未执行以及原因,而不是测试失败或完全忽略测试。
为了补充这些测试跳过行为,Django 提供了两个额外的跳过装饰器。 这些装饰器不是测试通用布尔值,而是检查数据库的功能,如果数据库不支持特定的命名功能,则跳过测试。
装饰器使用字符串标识符来描述数据库功能。 此字符串对应于数据库连接要素类的属性。 有关可用作跳过测试基础的数据库功能的完整列表,请参阅 django.db.backends.BaseDatabaseFeatures
类。
- skipIfDBFeature(*feature_name_strings)
如果支持所有指定的数据库功能,请跳过装饰测试或 TestCase
。
例如,如果数据库支持事务,则不会执行以下测试(例如,它会在 PostgreSQL 下运行 不 ,但它会在带有 MyISAM 表的 MySQL 下运行):
class MyTests(TestCase):
@skipIfDBFeature('supports_transactions')
def test_transaction_behavior(self):
# ... conditional test code
pass
- skipUnlessDBFeature(*feature_name_strings)
如果任何指定的数据库功能 不支持 ,则跳过装饰测试或 TestCase
。
例如,仅当数据库支持事务时才会执行以下测试(例如,它将在 PostgreSQL 下运行,但在 MySQL 下使用 MyISAM 表 不是 ):
class MyTests(TestCase):
@skipUnlessDBFeature('supports_transactions')
def test_transaction_behavior(self):
# ... conditional test code
pass