Django提供了一组小工具,在写测试时派上用场。
测试客户端是一个Python类,作为一个虚拟的Web浏览器,允许您测试您的视图,并与您的Django供电的应用程序以编程方式交互。
你可以用测试客户端做的一些事情是:
请注意,测试客户端不是要替代Selenium或其他“浏览器内”框架。 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框架。 这有助于使单元测试快速运行。
检索网页时,请记住指定网址的路径,而不是整个网域。 例如,这是正确的:
>>> c.get('/login/')
这是不正确的:
>>> c.get('https://www.example.com/login/')
测试客户端无法检索不受您的Django项目驱动的网页。 如果需要检索其他网页,请使用Python标准库模块,例如urllib
。
要解析网址,测试客户端将使用您的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
类发出请求。
客户端
(enforce_csrf_checks = False,** defaults)[source] ¶它在建设时不需要论证。 但是,您可以使用关键字参数指定一些默认标头。 例如,这会在每个请求中发送User-Agent
HTTP标头:
>>> c = Client(HTTP_USER_AGENT='Mozilla/5.0')
来自extra
关键字参数的值传递给get()
,post()
等。 优先于传递给类构造函数的默认值。
enforce_csrf_checks
参数可用于测试CSRF保护(参见上文)。
一旦您有一个Client
实例,您就可以调用以下任何方法:
get
(path,data = None,follow = False,secure = False ,** extra)[source] ¶在提供的Response
上发出GET请求,并返回path
对象,下面将对此进行说明。
data
字典中的键值对用于创建GET数据有效内容。 像这样:
>>> c = Client()
>>> c.get('/customers/details/', {'name': 'fred', 'age': 7})
...将导致GET请求的评估等同于:
/customers/details/?name=fred&age=7
extra
关键字arguments参数可用于指定要在请求中发送的标头。 像这样:
>>> c = Client()
>>> c.get('/customers/details/', {'name': 'fred', 'age': 7},
... HTTP_X_REQUESTED_WITH='XMLHttpRequest')
...将发送HTTP头HTTP_X_REQUESTED_WITH
到详细信息视图,这是测试使用django.http.HttpRequest.is_ajax()
方法的代码路径的好方法。
CGI规范
通过**extra
发送的标头应遵循CGI规范。
例如,模拟从浏览器到服务器的HTTP请求中发送的不同“主机”标头应作为HTTP_HOST
传递。
如果您已经有以URL编码形式的GET参数,则可以使用该编码,而不是使用data参数。 例如,先前的GET请求也可以被提出为:
>>> c = Client()
>>> c.get('/customers/details/?name=fred&age=7')
如果您提供的网址包含编码的GET数据和数据参数,则数据参数优先。
如果您将follow
设置为True
,则客户端将跟踪任何重定向,并且将在包含中间网址元组的响应对象中设置redirect_chain
属性和状态代码。
如果您有重定向到/next/
的网址/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)[source] ¶在提供的Response
上发出POST请求,并返回path
对象,这在下面进行了说明。
data
字典中的键值对用于提交POST数据。 像这样:
>>> c = Client()
>>> c.post('/login/', {'name': 'fred', 'passwd': 'secret'})
...将导致对此URL的POST请求的评估:
/login/
...使用此POST数据:
name=fred&passwd=secret
如果您为XML有效内容提供data
(例如text / xml),则content_type
的内容将作为POST请求,使用HTTP content_type
标头中的Content-Type
。
如果不为data
提供值,则content_type
中的值将以内容类型multipart / form-data传输。 在这种情况下,data
中的键值对将被编码为一个多部分消息,并用于创建POST数据有效负载。
要为给定键提交多个值 - 例如,为&lt; select multiple>
指定选择,列表或元组。 例如,choices
的此值将为名为data
的字段提交三个选定值:
{'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以确定用户是否是访问者。
如果您将True
设置为follow
,则客户端将跟踪任何重定向,并且将在包含中间网址元组的响应对象中设置redirect_chain
属性和状态代码。
如果您将True
设置为secure
,则客户端将模拟HTTPS请求。
head
(path,data = None,follow = False,secure = False ,** extra)[source] ¶在提供的Response
上执行HEAD请求,并返回path
对象。 此方法的工作方式与Client.get()
一样,除了它之外,包括follow
,secure
和extra
options
(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)[source]¶在提供的Response
上执行OPTIONS请求,并返回path
对象。 用于测试RESTful接口。
当提供Content-Type
时,它用作请求主体,并且data
头设置为content_type
。
secure
,follow
和extra
参数的作用与Client.get()
相同。
put
(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)[source]¶在提供的Response
上发出PUT请求,并返回path
对象。 用于测试RESTful接口。
当提供Content-Type
时,它用作请求主体,并且data
头设置为content_type
。
secure
,follow
和extra
参数的作用与Client.get()
相同。
patch
(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)[source]¶在提供的Response
上发出PATCH请求,并返回path
对象。 用于测试RESTful接口。
secure
,follow
和extra
参数的作用与Client.get()
相同。
delete
(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra)[source]¶在提供的path
上执行DELETE请求,并返回一个Response
对象。 用于测试RESTful接口。
当提供Content-Type
时,它用作请求主体,并且data
头设置为content_type
。
secure
,follow
和extra
参数的作用与Client.get()
相同。
trace
(path, follow=False, secure=False, **extra)[source]¶在提供的Response
上执行TRACE请求,并返回path
对象。 用于模拟诊断探头。
与其他请求方法不同,为了符合 RFC 7231#section-4.3.8,不提供data
作为关键字参数,要求TRACE请求不得有身体。
secure
,follow
和extra
参数的作用与Client.get()
相同。
login
(**credentials)[source]¶如果您的网站使用Django的authentication system,并且您处理用户登录,则可以使用测试客户端的login()
方法来模拟用户登录网站的效果。
调用此方法后,测试客户端将拥有通过任何可能构成视图一部分的基于登录的测试所需的所有Cookie和会话数据。
credentials
参数的格式取决于您使用的authentication backend(由您的AUTHENTICATION_BACKENDS
设置配置)。 如果您使用的是由Django提供的标准认证后端(credentials
),则ModelBackend
应该是用户的用户名和密码,
>>> 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)或测试夹具。 请记住,如果您希望测试用户拥有密码,则不能通过直接设置password属性来设置用户的密码 - 您必须使用set_password()
函数来存储正确的散列密码。 或者,您可以使用create_user()
助手方法创建具有正确散列密码的新用户。
在以前的版本中,不允许非活动用户登录(is_active=False
)。
force_login
(user, backend=None)[source]¶如果您的站点使用Django的authentication
system,您可以使用force_login()
方法来模拟用户登录站点的效果。 当测试需要用户登录并且用户登录的详细信息不重要时,请使用此方法而不是login()
。
与login()
不同,该方法跳过验证和验证步骤:不允许用户(is_active=False
)登录,不需要提供用户凭据。
用户将其backend
属性设置为backend
参数(它应该是一个虚线的Python路径字符串)或settings.AUTHENTICATION_BACKENDS[0]
如果没有提供值。
通过login()
调用的authenticate()
函数通常会像这样注释用户。
该方法比login()
更快,因为昂贵的密码散列算法被绕过。 Also, you can speed up
login()
by using a weaker hasher while testing.
logout
()[source]¶如果您的网站使用Django的authentication system,则可以使用logout()
方法模拟用户从您的网站注销的效果。
调用此方法后,测试客户端将所有Cookie和会话数据清除为默认值。 后续请求将显示为来自AnonymousUser
。
post()
和get()
方法都会返回Response
对象。 This
Response
object is not the same as the HttpResponse
object returned
by Django views; the test response object has some additional data useful for
test code to verify.
具体来说,Response
对象具有以下属性:
Response
¶client
¶用于生成导致响应的请求的测试客户端。
content
¶身体的反应,作为一个毕业生。 这是视图呈现的最终页面内容,或任何错误消息。
context
¶用于呈现产生响应内容的模板的模板Context
实例。
如果呈现的页面使用多个模板,则Context
将是context
对象的列表,按照它们的呈现顺序。
无论渲染期间使用的模板数量如何,都可以使用[]
运算符检索上下文值。 例如,可以使用以下方式检索上下文变量name
:
>>> response = client.get('/foo/')
>>> response.context['name']
'Arthur'
不使用Django模板?
仅当使用DjangoTemplates
后端时,才会填充此属性。
如果您使用另一个模板引擎,则context_data
可能是具有该属性的响应的合适替代方案。
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']
确定响应的内容类型。
如果将测试客户端指向引发异常的视图,那么该异常将在测试用例中可见。 然后可以使用标准 尝试 ... 除
块或assertRaises()
来测试异常。
对测试客户端不可见的唯一例外是Http404
,PermissionDenied
,SystemExit
和SuspiciousOperation
。 Django在内部捕获这些异常并将它们转换为适当的HTTP响应代码。 在这些情况下,您可以在测试中检查response.status_code
。
测试客户端是有状态的。 如果响应返回cookie,那么该cookie将存储在测试客户端中,并与所有后续的post()
和get()
请求一起发送。
不遵循这些cookie的过期政策。 如果您希望Cookie过期,请手动删除或创建新的Client
实例(这将有效删除所有Cookie)。
测试客户机具有存储持久状态信息的两个属性。 您可以作为测试条件的一部分访问这些属性。
Python SimpleCookie
对象,包含所有客户端Cookie的当前值。 有关更多信息,请参阅http.cookies
模块的文档。
Client.
session
¶包含会话信息的类字典对象。 有关详细信息,请参阅session documentation。
要修改会话然后保存它,它必须首先存储在变量中(因为每次访问此属性时都会创建一个新的SessionStore
):
def test_something(self):
session = self.client.session
session['somekey'] = 'test'
session.save()
当测试支持国际化和本地化的应用程序时,您可能需要设置测试客户端请求的语言。 这样做的方法取决于是否启用了LocaleMiddleware
。
如果启用了中间件,则可以通过创建名称为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.")
更多细节在How Django discovers language preference。
如果中间件未启用,则可以使用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.")
更多详细信息,请参见Explicitly setting the active language。
以下是使用测试客户端的简单单元测试:
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提供了这个基类的一些扩展:
将正常的unittest.TestCase
转换为任何子类很容易:将测试的基类从unittest.TestCase
更改为子类。 所有标准的Python单元测试功能将可用,并且将会增加一些有用的添加,如下面的每个部分所述。
SimpleTestCase
¶添加此功能的unittest.TestCase
的子类:
raises a certain exception
。rendering and error treatment
。HTML responses for the presence/lack of a given fragment
。has/hasn't been used to generate a given
response content
。redirect
是由应用执行的。HTML fragments
的等式/不等式或containment
。XML fragments
的相等/不等。JSON fragments
的相等性。client
Client
。如果您的测试进行任何数据库查询,请使用子类TransactionTestCase
或TestCase
。
SimpleTestCase。
allow_database_queries T0> ¶ T1>
默认情况下,SimpleTestCase
不允许数据库查询。 这有助于避免执行会影响其他测试的写查询,因为每个SimpleTestCase
测试不在事务中运行。 如果您不担心此问题,可以通过将allow_database_queries
类属性设置为True
来禁用此行为。
警告
SimpleTestCase
及其子类(例如TestCase
,...)依赖于setUpClass()
和tearDownClass()
执行一些全班初始化(例如覆盖设置)。 如果需要重写这些方法,不要忘记调用super
实现:
class MyTestCase(TestCase):
@classmethod
def setUpClass(cls):
super(MyTestCase, cls).setUpClass()
...
@classmethod
def tearDownClass(cls):
...
super(MyTestCase, cls).tearDownClass()
如果在setUpClass()
中引发异常,请务必考虑Python的行为。 如果发生这种情况,那么类中的测试和tearDownClass()
都不会运行。 在django.test.TestCase
的情况下,这将泄漏在super()
中创建的事务,这会导致各种症状,包括某些平台上的分段错误(在macOS上报告)。 如果要在setUpClass()
中有意引发诸如unittest.SkipTest
的异常,请确保在调用super()
之前执行此操作避免这个。
TransactionTestCase
¶TransactionTestCase
继承自SimpleTestCase
以添加一些特定于数据库的功能:
fixtures
。assert*
方法。Django的TestCase
类是一种更常用的TransactionTestCase
子类,它利用数据库事务处理功能,将数据库的重置过程加快到每个开始时的已知状态测试。 然而,这样做的一个后果是,一些数据库行为不能在Django TestCase
类中测试。 例如,您不能测试一个代码块是否在事务中执行,如使用select_for_update()
时所需。 在这些情况下,您应该使用TransactionTestCase
。
TestCase
和TransactionTestCase
是完全相同的,除了数据库重置为已知状态的方式以及测试代码测试提交和回滚效果的能力:
TransactionTestCase
会重置数据库。 TransactionTestCase
可以调用提交和回滚,并观察这些调用对数据库的影响。TestCase
在测试后不会截断表。
相反,它将测试代码包含在测试结束时回滚的数据库事务中。 这保证在测试结束时的回滚将数据库恢复到其初始状态。警告
在不支持回滚的数据库(例如具有MyISAM存储引擎的MySQL)上运行的TestCase
以及TransactionTestCase
的所有实例将在测试结束时回滚从测试数据库中删除所有数据。
Apps will not see their data reloaded;如果您需要此功能(例如,第三方应用程序应启用此功能),您可以设置serialized_rollback = True t2 >
TestCase
正文内。
TestCase
¶这是用于在Django中编写测试的最常用的类。 它继承自TransactionTestCase
(以及扩展名SimpleTestCase
)。
如果您的Django应用程序不使用数据库,请使用SimpleTestCase
。
班上:
atomic()
块中:一个用于整个类,一个用于每个测试。 因此,如果要测试某些特定的数据库事务行为,请使用TransactionTestCase
。在每个测试结束时添加了对可延迟数据库约束的检查。
它还提供了一种额外的方法:
TestCase.
setUpTestData
()[source]¶上述类级别TestCase
块允许在类级别创建初始数据,对于整个atomic
一次。 与使用setUp()
相比,此技术允许更快的测试。
像这样:
from django.test import 测试用例
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
¶LiveServerTestCase
基本上与TransactionTestCase
相同,具有一个额外的功能:它在设置的后台启动一个活动的Django服务器,并在拆卸时将其关闭。
这允许使用除了Django dummy client(例如,Selenium客户端)之外的自动测试客户端,在浏览器中执行一系列功能测试,并模拟真实用户的操作。
实时服务器侦听localhost
,并绑定到使用操作系统分配的空闲端口的端口0。 在测试期间,可以使用self.live_server_url
访问服务器的URL。
在旧版本中,Django尝试了一个预定义的端口范围,可以以各种方式进行定制,包括DJANGO_LIVE_TEST_SERVER_ADDRESS
环境变量。 这被删除,有利于更简单的“绑定到端口0”技术。
为了演示如何使用LiveServerTestCase
,我们来写一个简单的Selenium测试。 首先呢,你需要用pip命令安装 selenium package 到你的Python路径里面:
$ pip install selenium
然后,向应用程序的测试模块添加myapp/tests.py
测试(例如:LiveServerTestCase
)。 对于这个例子,我们假设你正在使用staticfiles
应用程序,并希望在执行测试期间提供静态文件,类似于开发时间与DEBUG=True
,即不需要使用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(MySeleniumTests, cls).setUpClass()
cls.selenium = WebDriver()
cls.selenium.implicitly_wait(10)
@classmethod
def tearDownClass(cls):
cls.selenium.quit()
super(MySeleniumTests, cls).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()
最后,您可以运行测试如下:
$ ./manage.py test myapp.tests.MySeleniumTests.test_login
此示例将自动打开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'))
这里的棘手的事情是,真的没有一个“页面加载”,尤其是在现代Web应用程序中,在服务器生成初始文档后动态生成HTML。 因此,简单地检查响应中是否存在<body>
可能不一定适用于所有用例。 有关详细信息,请参阅Selenium常见问题和Selenium文档。
SimpleTestCase.
client
¶每个测试用例在a django.test。*测试用例
实例可以访问Django测试客户端的一个实例。 此客户端可以作为self.client
访问。 每个测试都重新创建此客户端,因此您不必担心从一个测试到另一个测试的状态(例如Cookie)。
这意味着,不是在每个测试中实例化Client
:
import unittest
from django.test import Client
class SimpleTest(unittest.测试用例):
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 测试用例
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 TestCase, Client
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()
中,但是您也可以使用fixture。
fixture是Django知道如何导入到数据库中的数据集合。 例如,如果您的网站有用户帐户,您可能会设置一个假的用户帐户,以便在测试期间填充您的数据库。
创建fixture的最直接的方法是使用manage.py dumpdata
命令。 这假定您的数据库中已经有一些数据。 有关详细信息,请参阅dumpdata
documentation
。
Once you’ve created a fixture and placed it in a fixtures
directory in one
of your INSTALLED_APPS
, you can use it in your unit tests by
specifying a fixtures
class attribute on your django.test.TestCase
subclass:
from django.test import 测试用例
from myapp.models import Animal
class AnimalTestCase(TestCase):
fixtures = ['mammals.json', 'birds']
def setUp(self):
# Test definitions as before.
call_setup_methods()
def testFluffyAnimals(self):
# A test that uses the fixtures.
call_some_test_code()
这里具体是什么会发生:
setUp()
之前,Django将刷新数据库,将数据库返回到调用migrate
之后的状态。birds
的JSON夹具,然后安装任何名为mammals
的夹具。 有关定义和安装灯具的更多详细信息,请参阅loaddata
文档。For performance reasons, TestCase
loads fixtures once for the entire
test class, before setUpTestData()
, instead of before each
test, and it uses transactions to clean the database before each test. 无论如何,您可以确定测试的结果不会受到另一个测试或测试执行顺序的影响。
默认情况下,fixture仅加载到default
数据库中。 如果您使用多个数据库并设置multi_db=True
,fixture将被加载到所有数据库。
如果您的应用程序提供了视图,您可能需要包括使用测试客户端来执行这些视图的测试。 但是,最终用户可以在自己选择的任何URL上自由地在应用程序中部署视图。 使用@override_settings(ROOT_URLCONF=...)
装饰您的测试类或测试方法以进行URLconf配置。
TransactionTestCase.
multi_db
¶Django设置与设置文件中的DATABASES
定义中定义的每个数据库对应的测试数据库。 但是,运行Django TestCase所需的大部分时间由调用flush
消耗,确保在每次测试运行开始时都有一个干净的数据库。 如果您有多个数据库,则需要多次刷新(每个数据库一次),这可能是一个耗时的活动 - 特别是如果您的测试不需要测试多数据库活动。
作为优化,Django仅在每次测试运行开始时刷新default
数据库。 如果您的设置包含多个数据库,并且测试需要每个数据库都是干净的,则可以使用测试套件上的multi_db
属性请求完全刷新。
像这样:
class TestMyViews(TestCase):
multi_db = True
def test_index_page_view(self):
call_some_test_code()
此测试用例将在运行test_index_page_view
之前冲洗所有测试数据库。
multi_db
标志也会影响加载了TransactionTestCase.fixtures
的数据库。 默认情况下(当default
时),fixture仅加载到multi_db=False
数据库中。
如果multi_db=True
,fixture将加载到所有数据库中。
警告
使用以下功能临时更改测试中的设置值。
不要直接操作django.conf.settings
,因为Django在这种操作后不会恢复原始值。
为了测试目的,在运行测试代码之后临时更改设置并恢复为原始值通常很有用。 对于这种用例,Django提供了一个标准的Python上下文管理器(参见 PEP 343),名为settings()
from django.test import 测试用例
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
块中的代码的LOGIN_URL
设置,然后将其值重置为之前的状态。
它可以证明难以重新定义包含值列表的设置。 在实践中,添加或删除值通常就足够了。 modify_settings()
上下文管理器使其变得容易:
from django.test import 测试用例
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
。
如果要覆盖测试方法的设置,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/')
同样,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('/')
# ...
注
当给一个类时,这些装饰器直接修改类并返回它;它们不会创建并返回其修改的副本。 因此,如果您尝试调整上述示例以将返回值分配给不同于MiddlewareTestCase
或LoginTestCase
的名称,您可能会惊讶地发现原始测试用例类仍然同样受到装饰师的影响。 对于给定类,modify_settings()
始终应用于override_settings()
之后。
警告
设置文件包含一些仅在Django内部初始化期间参考的设置。 如果使用django.conf.settings
更改这些设置,如果通过override_settings
模块访问它,设置会更改,但是Django的内部访问方式不同。 有效地,使用这些设置使用override_settings()
或modify_settings()
可能不会做你期望做的。
我们不建议更改DATABASES
设置。 改变CACHES
设置是可能的,但如果你使用使用缓存的内部,如django.contrib.sessions
,有点棘手。 例如,您必须在使用缓存会话并覆盖CACHES
的测试中重新初始化会话后端。
最后,避免将您的设置作为模块级常量别名,因为override_settings()
将不适用于这些值,因为它们仅在首次导入模块时进行评估。
您还可以在设置被覆盖后通过删除设置来模拟缺少设置,如下所示:
@override_settings()
def test_something(self):
del settings.LOGIN_URL
...
覆盖设置时,请确保处理您的应用代码使用缓存或类似功能的情况,即使设置更改也保留状态。 Django提供了django.test.signals.setting_changed
信号,允许您注册回调以清除设置,否则在更改设置时重置状态。
Django本身使用这个信号来重置各种数据:
覆盖设置 | 数据复位 |
---|---|
USE_TZ,TIME_ZONE | 数据库时区 |
TEMPLATES | 模板引擎 |
SERIALIZATION_MODULES | 串行器缓存 |
LOCALE_PATHS,LANGUAGE_CODE | 默认翻译和加载翻译 |
MEDIA_ROOT,DEFAULT_FILE_STORAGE | 默认文件存储 |
由于Python的普通unittest.TestCase
类实现了断言方法,例如assertTrue()
和assertEqual()
,Django的定制TestCase
大多数断言方法给出的失败消息可以使用msg_prefix
参数定制。 此字符串将作为断言生成的任何失败消息的前缀。 这允许您提供其他详细信息,可帮助您确定测试套件中的故障位置和原因。
SimpleTestCase。
assertRaisesMessage
(expected_exception, expected_message, callable, *args, **kwargs)[source]¶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')
自1.9版以来已弃用 作为callable_obj
的关键字参数传递callable
已被弃用。 将可呼叫作为位置参数传递。
SimpleTestCase.
assertFieldOutput
(fieldclass, valid, invalid, field_args=None, field_kwargs=None, empty_value='')[source]¶断言表单字段在各种输入中正确运行。
参数: |
|
---|
例如,以下代码测试a@a.com
接受EmailField
作为有效的电子邮件地址,但拒绝具有合理错误的aaa
信息:
self.assertFieldOutput(EmailField, {'a@a.com': 'a@a.com'}, {'aaa': ['Enter a valid email address.']})
SimpleTestCase.
assertFormError
(response, form, field, errors, msg_prefix='')[source]¶断言窗体上的字段在表单上呈现时会提供所提供的错误列表。
Form
是在模板上下文中给出的form
实例的名称。
field
是要检查的表单上的字段的名称。 如果None
的值为field
,则将检查非字段错误(可通过form.non_field_errors()
访问的错误)。
errors
是预期为表单验证结果的错误字符串或错误字符串列表。
SimpleTestCase.
assertFormsetError
(response, formset, form_index, field, errors, msg_prefix='')[source]¶断言formset
在呈现时引发提供的错误列表。
Formset
是在模板上下文中给出的formset
实例的名称。
Formset
是form_index
内的表单编号。 如果None
的值为form_index
,则将检查非格式错误(可通过formset.non_form_errors()
访问的错误)。
field
是要检查的表单上的字段的名称。 如果None
的值为field
,则将检查非字段错误(可通过form.non_field_errors()
访问的错误)。
errors
是预期为表单验证结果的错误字符串或错误字符串列表。
SimpleTestCase.
assertContains
(response, text, count=None, status_code=200, msg_prefix='', html=False)[source]¶断言status_code
实例生成给定的Response
,并且该text
显示在响应的内容中。 如果提供了text
,则count
必须在响应中出现正好count
次数。
将True
设置为html
,将text
作为HTML。 与响应内容的比较将基于HTML语义,而不是逐个字符的等同。 在大多数情况下忽略空白,属性排序不重要。 有关详细信息,请参见assertHTMLEqual()
。
SimpleTestCase.
assertNotContains
(response, text, status_code=200, msg_prefix='', html=False)[source]¶认为status_code
实例产生给定的Response
,并且text
不会不会出现在响应的内容中。
将True
设置为html
,将text
作为HTML。 与响应内容的比较将基于HTML语义,而不是逐个字符的等同。 在大多数情况下忽略空白,属性排序不重要。 有关详细信息,请参见assertHTMLEqual()
。
SimpleTestCase.
assertTemplateUsed
(response, template_name, msg_prefix='', count=None)[source]¶断言具有给定名称的模板用于呈现响应。
名称是一个字符串,例如'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='')[source]¶声明使用给定名称的模板不来呈现响应。
您可以使用与assertTemplateUsed()
相同的方式作为上下文管理器。
SimpleTestCase。
assertRedirects
(response, expected_url, status_code=302, target_status_code=200, msg_prefix='', fetch_redirect_response=True)[source]¶Asserts that the response returned a status_code
redirect status,
redirected to expected_url
(including any GET
data), and that the
final page was received with target_status_code
.
如果您的请求使用expected_url
参数,则follow
和target_status_code
将是重定向链最后一点的网址和状态代码。
如果False
为fetch_redirect_response
,则不会加载最后一页。 由于测试客户端无法获取外部URL,所以如果expected_url
不是Django应用程序的一部分,那么这一点尤其有用。
在两个URL之间进行比较时,会正确处理Scheme。 如果在我们重定向到的位置中没有指定任何方案,则使用原始请求的方案。 如果存在,expected_url
中的方案是用于进行比较的方案。
自1.9版以来已弃用 host
参数已被弃用,因为重定向不再被强制为绝对URL。
SimpleTestCase。
assertHTMLEqual
(html1,html2,msg =无)[source] ¶ T6>断言字符串html2
和html1
是相等的。 比较基于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>'
)
html2
和html1
必须是有效的HTML。 如果其中一个无法解析,则会引发AssertionError
。
出错时的输出可以用msg
参数定制。
SimpleTestCase。
assertHTMLNotEqual
(html1,html2,msg =无)[source] ¶ T6>认为字符串html2
和html1
的不是相等。 比较基于HTML语义。 有关详细信息,请参见assertHTMLEqual()
。
html2
和html1
必须是有效的HTML。 如果其中一个无法解析,则会引发AssertionError
。
出错时的输出可以用msg
参数定制。
SimpleTestCase。
assertXMLEqual
(xml1,xml2,msg =无)[source] ¶ T6>断言字符串xml2
和xml1
相等。 比较基于XML语义。 与assertHTMLEqual()
类似,对解析的内容进行比较,因此仅考虑语义差异,而不考虑语法差异。 当在任何参数中传递无效的XML时,始终会出现AssertionError
,即使两个字符串都相同。
出错时的输出可以用msg
参数定制。
SimpleTestCase。
assertXMLNotEqual
(xml1,xml2,msg =无)[source] ¶ T6>断言字符串xml2
和xml1
的不是相等。 比较基于XML语义。 有关详细信息,请参见assertXMLEqual()
。
出错时的输出可以用msg
参数定制。
SimpleTestCase。
assertInHTML
(needle, haystack, count=None, msg_prefix='')[source]¶断言HTML片段haystack
包含在needle
中。
如果指定needle
整数参数,则将严格验证count
出现的次数。
在大多数情况下,空白被忽略,属性排序不重要。 传入的参数必须是有效的HTML。
SimpleTestCase。
assertJSONEqual
(raw,expected_data,msg =无)[source] ¶ T6>断言JSON片段expected_data
和raw
是相等的。
通常的JSON非重要空格规则适用于将重量级委派给json
库。
出错时的输出可以用msg
参数定制。
SimpleTestCase。
assertJSONNotEqual
(raw,expected_data,msg =无)[source] ¶ T6>断言JSON片段expected_data
和raw
的不是相等。
有关详细信息,请参见assertJSONEqual()
。
出错时的输出可以用msg
参数定制。
TransactionTestCase。
assertQuerysetEqual
(qs, values, transform=repr, ordered=True, msg=None)[source]¶断言查询集values
返回值qs
。
使用函数transform
执行qs
和values
的内容的比较;默认情况下,这意味着比较每个值的repr()
。 如果repr()
未提供唯一或有帮助的比较,则可以使用任何其他可调用项。
默认情况下,比较也依赖于顺序。 如果qs
不提供隐式排序,则可以将ordered
参数设置为False
,将比较变为collections.Counter
比较。
如果顺序未定义(如果给定的ValueError
不是有序的,并且比较是针对多个有序值),则会引发qs
。
出错时的输出可以用msg
参数定制。
TransactionTestCase。
assertNumQueries
(num, func, *args, **kwargs)[source]¶断言当使用*args
和func
调用**kwargs
时,将执行num
数据库查询。
如果kwargs
中存在"using"
键,它将用作要检查查询数的数据库别名。 如果你想用lambda
参数调用一个函数,你可以通过用using
包装调用来添加一个额外的参数:
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):
...
然后,您可以选择要运行的测试。 例如,只运行快速测试:
$ ./manage.py test --tag=fast
或者运行快速测试和核心测试(尽管速度很慢):
$ ./manage.py test --tag=fast --tag=core
您也可以通过标签排除测试。 运行核心测试如果不慢:
$ ./manage.py test --tag=core --exclude-tag=slow
test --exclude-tag
优先于test --tag
,所以如果一个测试有两个标签,你选择其中一个标签并排除其他标签,测试将不会运行。
如果您的任何Django视图使用Django’s email functionality发送电子邮件,您可能不希望在每次使用该视图运行测试时发送电子邮件。 因此,Django的测试运行器会自动将所有Django发送的电子邮件重定向到一个虚拟发件箱。 这使您可以测试发送电子邮件的每个方面 - 从发送到每封邮件内容的邮件数量,而不实际发送邮件。
测试运行器通过用测试后端透明地替换普通电子邮件后端来实现这一点。 (不要担心,这对Django之外的任何其他电子邮件发件人(例如您计算机的邮件服务器)(如果您正在运行)没有任何影响。)
django.core.mail。
发件箱 T0> ¶ T1>
在测试运行期间,每封发出的电子邮件都保存在django.core.mail.outbox
中。 这是所有已发送的EmailMessage
实例的简单列表。
locmem
属性是在使用outbox
电子邮件后端时仅在创建的特殊属性。 它通常不作为django.core.mail
模块的一部分存在,您不能直接导入它。 下面的代码显示了如何正确访问此属性。
下面是一个示例测试,检查django.core.mail.outbox
的长度和内容:
from django.core import mail
from django.test import 测试用例
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')
如前所述previously,测试发件箱在Django *TestCase
中的每个测试开始时都被清空。 要手动清空发件箱,请将空列表分配给mail.outbox
:
from django.core import mail
# Empty the test outbox
mail.outbox = []
可以使用call_command()
函数测试管理命令。 输出可以重定向到StringIO
实例:
from django.core.management import call_command
from django.test import 测试用例
from django.utils.six import StringIO
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
类。
如果支持所有命名的数据库功能,请跳过装饰测试或TestCase
。
例如,如果数据库支持事务(例如,不在PostgreSQL下运行,但在MySQL with MyISAM表下),则不会执行以下测试:
class MyTests(TestCase):
@skipIfDBFeature('supports_transactions')
def test_transaction_behavior(self):
# ... conditional test code
pass
如果任何命名的数据库功能不支持,请跳过装饰测试或TestCase
。
例如,如果数据库支持事务(例如,它将在PostgreSQL下运行,但在MySQL和MyISAM表下的不是),则将执行以下测试:
class MyTests(TestCase):
@skipUnlessDBFeature('supports_transactions')
def test_transaction_behavior(self):
# ... conditional test code
pass
2017年9月6日