测试工具
Django提供了一组小工具,在写测试时派上用场。
测试客户端
测试客户端是一个Python类,作为一个虚拟的Web浏览器,允许您测试您的视图,并与您的Django供电的应用程序以编程方式交互。
你可以用测试客户端做的一些事情是:
- 模拟对URL的GET和POST请求,并观察响应 - 从低级HTTP(结果头和状态代码)到页面内容的一切。
- 查看重定向链(如果有),并在每个步骤中检查网址和状态代码。
- 测试给定的请求是否由给定的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
'<!DOCTYPE html...'
正如此示例所建议的,您可以在Python交互式解释器的会话中实例化Client
。
请注意测试客户端如何工作的几个重要的事情:
测试客户端不要求Web服务器正在运行。事实上,它将运行很好,没有Web服务器运行在所有!这是因为它避免了HTTP的开销,直接处理Django框架。这有助于使单元测试快速运行。
检索网页时,请记住指定网址的路径,而不是整个网域。例如,这是正确的:
>>> c.get('/login/')
这是不正确的:
>>> c.get('http://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
类发出请求。
class Client
(enforce_csrf_checks=False, **defaults)
它在建设时不需要论证。但是,您可以使用关键字参数指定一些默认标头。例如,这会在每个请求中发送User-Agent
HTTP标头:
>>> c = Client(HTTP_USER_AGENT='Mozilla/5.0')
传递给get()
,post()
等的extra
关键字参数的值优先于传递给类构造函数的默认值。
enforce_csrf_checks
参数可用于测试CSRF保护(参见上文)。
一旦您有一个Client
实例,您就可以调用以下任何方法:
get
(path, data=None, follow=False, secure=False, **extra)
New in Django 1.7:
已添加secure
参数。
在提供的path
上发出GET请求,并返回Response
对象,下面将对此进行说明。
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)
在提供的path
上发出POST请求,并返回Response
对象,这在下面进行了说明。
data
字典中的键值对用于提交POST数据。例如:
>>> c = Client()
>>> c.post('/login/', {'name': 'fred', 'passwd': 'secret'})
…将导致对此URL的POST请求的评估:
/login/
…用这个POST数据:
name=fred&passwd=secret
如果您为XML有效内容提供content_type
(例如text / xml),则data
的内容将作为POST请求,使用HTTP Content-Type
标头中的content_type
。
如果不为content_type
提供值,则data
中的值将以内容类型multipart / form-data传输。在这种情况下,data
中的键值对将被编码为一个多部分消息,并用于创建POST数据有效负载。
要为给定键提交多个值 - 例如,为&lt; 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
)作为文件句柄。
New in Django 1.8:
添加了使用类文件对象的能力。
请注意,如果您希望对多个post()
调用使用相同的文件句柄,则需要在文章之间手动重置文件指针。最简单的方法是在文件提供给post()
之后手动关闭文件,如上所示。
您还应确保以允许读取数据的方式打开文件。如果您的文件包含二进制数据,如图像,这意味着您需要以rb
(读取二进制)模式打开该文件。
extra
参数的作用与Client.get()
相同。
如果您通过POST请求的URL包含已编码的参数,则这些参数将在请求中可用。GET数据。例如,如果您提出请求:
>>> c.post('/login/?visitor=true', {'name': 'fred', 'passwd': 'secret'})
…处理此请求的视图可以询问请求。POST以检索用户名和密码,并且可以询问请求。GET以确定用户是否是访问者。
如果您将follow
设置为True
,则客户端将跟踪任何重定向,并且将在包含中间网址元组的响应对象中设置redirect_chain
属性和状态代码。
如果您将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)
New in Django 1.8.
在提供的path
上执行TRACE请求,并返回Response
对象。用于模拟诊断探头。
与其他请求方法不同,为了符合 RFC 2616,data
不作为关键字参数提供,这要求TRACE请求不应有一个实体体。
follow
,secure
和extra
参数的作用与Client.get()
相同。
login
(**credentials)
如果您的网站使用Django的authentication system,并且您处理用户登录,则可以使用测试客户端的login()
方法来模拟用户登录网站的效果。
调用此方法后,测试客户端将拥有通过任何可能构成视图一部分的基于登录的测试所需的所有Cookie和会话数据。
credentials
参数的格式取决于您使用的authentication backend(由您的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()
returns True
if it the credentials were accepted and login was successful.
最后,您需要记住创建用户帐户,然后才能使用此方法。如上所述,测试运行器是使用测试数据库执行的,默认情况下不包含用户。因此,在生产站点上有效的用户帐户将无法在测试条件下工作。您需要创建用户作为测试套件的一部分 - 手动(使用Django模型API)或测试夹具。请记住,如果您希望测试用户拥有密码,则不能通过直接设置password属性来设置用户的密码 - 您必须使用set_password()
函数来存储正确的散列密码。或者,您可以使用create_user()
助手方法创建具有正确散列密码的新用户。
logout
()
如果您的网站使用Django的authentication system,则可以使用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'
request
刺激响应的请求数据。
wsgi_request
New in Django 1.7.
由生成响应的测试处理程序生成的WSGIRequest
实例。
status_code
响应的HTTP状态,作为整数。有关HTTP状态代码的完整列表,请参见 RFC 2616。
templates
用于渲染最终内容的Template
实例列表,按渲染顺序排列。对于列表中的每个模板,如果从文件加载模板,请使用template.name
获取模板的文件名。(名称是一个字符串,例如'admin/index.html'
。)
resolver_match
New in Django 1.8:
响应的实例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将存储在测试客户端中,并与所有后续的get()
和post()
请求一起发送。
不遵循这些cookie的过期政策。如果您希望Cookie过期,请手动删除或创建新的Client
实例(这将有效删除所有Cookie)。
测试客户机具有存储持久状态信息的两个属性。您可以作为测试条件的一部分访问这些属性。
Client.``cookies
Python SimpleCookie
对象,包含所有客户端Cookie的当前值。有关更多信息,请参阅http.cookies
模块的文档。
Client.``session
包含会话信息的类字典对象。有关详细信息,请参阅session documentation。
要修改会话然后保存它,它必须首先存储在变量中(因为每次访问此属性时都会创建一个新的SessionStore
):
def test_something(self):
session = self.client.session
session['somekey'] = 'test'
session.save()
例
以下是使用测试客户端的简单单元测试:
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.
测试用例。Django提供了这个基类的一些扩展:
Django单元测试类的层次结构
SimpleTestCase
class SimpleTestCase
unittest.
TestCase,它扩展它与一些基本功能,如:
- 保存和恢复Python警告机制状态。
- Some useful assertions like:
- Checking that a callable .
- 测试表单字段
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
。 - 验证HTTP
redirect
是由应用执行的。 - 稳健地测试两个
HTML fragments
的等式/不等式或containment
。 - 稳健地测试两个
XML fragments
的相等/不等。 - 稳健地测试两个
JSON fragments
的相等性。
- 使用modified settings运行测试的能力。
- 使用
client
Client
。 - 自定义测试时间
URL maps
。
如果您需要任何其他更复杂和重量级的Django特定功能,如:
- 测试或使用ORM。
- 数据库
fixtures
。 - 根据数据库后端功能测试skipping based on database backend features。
- 其余的专用
assert*
方法。
那么您应该改用TransactionTestCase
或TestCase
。
SimpleTestCase
继承自unittest.
测试用例。
警告
SimpleTestCase
及其子类(例如TestCase
,…)依赖于setUpClass()
和tearDownClass()
执行一些类的初始化(例如覆盖设置)。如果需要重写这些方法,不要忘记调用super
实现:
class MyTestCase(TestCase):
@classmethod
def setUpClass(cls):
super(MyTestCase, cls).setUpClass() # Call parent first
...
@classmethod
def tearDownClass(cls):
...
super(MyTestCase, cls).tearDownClass() # Call parent last
TransactionTestCase
class TransactionTestCase
Django的TestCase
类(如下所述)利用数据库事务工具来加快在每个测试开始时将数据库重置为已知状态的过程。然而,这样做的一个后果是,一些数据库行为不能在Django TestCase
类中测试。例如,您不能测试一个代码块是否在事务中执行,如使用select_for_update()
时所需。在这些情况下,您应该使用TransactionTestCase
。
Changed in Django 1.8:
在旧版本的Django中,事务提交和回滚的影响无法在TestCase
中测试。随着Django 1.8中旧式事务管理的弃用循环的完成,在TestCase
中不再禁用事务管理命令(例如transaction.commit()
)。
TransactionTestCase
和TestCase
是完全相同的,除了数据库重置为已知状态的方式以及测试代码测试提交和回滚效果的能力:
- 通过截断所有表,测试运行后,
TransactionTestCase
会重置数据库。TransactionTestCase
可以调用提交和回滚,并观察这些调用对数据库的影响。 - 另一方面,
TestCase
在测试后不会截断表。相反,它将测试代码包含在测试结束时回滚的数据库事务中。这保证在测试结束时的回滚将数据库恢复到其初始状态。
警告
在不支持回滚的数据库上运行的TestCase
(例如,具有MyISAM存储引擎的MySQL)以及TransactionTestCase
的所有实例将在测试结束时回滚从测试数据库中删除所有数据,并重新加载应用程序的初始数据而不进行迁移。
迁移will not see their data reloaded;如果您需要此功能(例如,第三方应用应启用此功能),您可以设置serialized_rollback = True t0 >在
TestCase内。
TransactionTestCase
继承自SimpleTestCase
。
测试用例
class TestCase
这个类提供了一些额外的功能,可以用来测试网站
正常转换unittest.
TestCase到Django TestCase
很简单:只需从'unittest.
TestCase’to 'django.test.TestCase'
。所有标准的Python单元测试功能将继续可用,但将增加一些有用的补充,包括:
- 自动加载夹具。
- 将测试包含在两个嵌套的
atomic
块中:一个用于整个类,一个用于每个测试。 - 创建一个TestClient实例。
- Django特定的断言用于测试重定向和形式错误。
classmethod TestCase.``setUpTestData
()
New in Django 1.8.
上述类级别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()
,否定速度优势。
警告
如果要测试一些特定的数据库事务行为,应该使用TransactionTestCase
作为TestCase
在atomic()
块中测试执行。
TestCase
继承自TransactionTestCase
。
LiveServerTestCase
class LiveServerTestCase
LiveServerTestCase
基本上与TransactionTestCase
相同,具有一个额外的功能:它在设置的后台启动一个活动的Django服务器,并在拆卸时将其关闭。这允许使用除了Django dummy client(例如,Selenium客户端)之外的自动测试客户端,在浏览器中执行一系列功能测试,并模拟真实用户的操作。
默认情况下,活动服务器的地址为'localhost:8081'
,在测试期间可以使用self.live_server_url
访问完整的URL。If you’d like to change the default address (in the case, for example, where the 8081 port is already taken) then you may pass a different one to the test
command via the --liveserver
option, for example:
$ ./manage.py test --liveserver=localhost:8082
更改默认服务器地址的另一种方法是在代码中某处设置<cite>DJANGOLIVE_TEST_SERVER_ADDRESS</cite>环境变量(例如,在[_custom test runner]($advanced.html#topics-testing-test-runner)中):
import os
os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = 'localhost:8082'
在测试由多个进程并行运行的情况下(例如,在几个并发的连续集成构建的上下文中),进程将竞争同一个地址,因此您的测试可能随机失败并显示“地址已在使用中”错误。为了避免此问题,您可以传递逗号分隔的端口列表或端口范围(至少与潜在的并行进程数一样多)。例如:
$ ./manage.py test --liveserver=localhost:8082,8090-8100,9000-9200,7041
然后,在测试执行期间,每个新的活测试服务器将尝试每个指定的端口,直到找到一个可用的并且接受它。
为了演示如何使用,让我们编写一个简单的Selenium测试。 首先呢,你需要用pip命令安装 selenium package 到你的Python路径里面:
$ pip install selenium
然后,向应用程序的测试模块添加LiveServerTestCase
测试(例如:myapp/tests.py
)。此测试的代码可能如下所示:
from django.test import LiveServerTestCase
from selenium.webdriver.firefox.webdriver import WebDriver
class MySeleniumTests(LiveServerTestCase):
fixtures = ['user-data.json']
@classmethod
def setUpClass(cls):
super(MySeleniumTests, cls).setUpClass()
cls.selenium = WebDriver()
@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客户端可以做的一小部分;有关详细信息,请参阅完整参考。
Changed in Django 1.7:
在旧版本中,LiveServerTestCase
依赖staticfiles contrib app在测试执行期间透明地提供静态文件。此功能已移至StaticLiveServerTestCase
子类,因此如果您需要the original behavior,请使用该子类。
LiveServerTestCase
现在只需在STATIC_ROOT
下的STATIC_URL
发布文件系统的内容。
注意
当使用内存中的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
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 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
如果数据库中没有任何数据,则对于数据库支持的Web站点的测试用例没有多大用处。为了方便将测试数据放入数据库,Django的自定义TransactionTestCase
类提供了加载fixtures的方法。
fixture是Django知道如何导入到数据库中的数据集合。例如,如果您的网站有用户帐户,您可能会设置一个假的用户帐户,以便在测试期间填充您的数据库。
创建fixture的最直接的方法是使用manage.py dumpdata
命令。这假定您的数据库中已经有一些数据。有关详细信息,请参阅dumpdata documentation
。
注意
如果您曾经运行过manage.py migrate
,您已经使用了一个甚至不知道它的灯具!当您第一次在数据库中调用migrate
时,Django会安装一个名为initial_data
的夹具。这为您提供了一种使用任何初始数据填充新数据库的方法,例如默认的一组类别。
可以使用manage.py loaddata
命令手动安装具有其他名称的装置。
初始SQL数据和测试
Django提供了将初始数据插入模型的第二种方法 - custom SQL hook。然而,该技术_不能用于提供用于测试目的的初始数据。_Django的测试框架在每次测试后刷新测试数据库的内容;因此,使用自定义SQL钩子添加的任何数据都将丢失。
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 TestCase
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
之后的状态。 - 然后,安装所有命名的夹具。在这个例子中,Django将安装任何名为
mammals
的JSON夹具,然后安装任何名为birds
的夹具。有关定义和安装灯具的更多详细信息,请参阅loaddata
文档。
对测试用例中的每个测试重复这个刷新/装载过程,因此您可以确定测试的结果不会受到另一个测试或测试执行的顺序的影响。
默认情况下,fixture仅加载到default
数据库中。如果您使用多个数据库并设置multi_db=True
,fixture将被加载到所有数据库。
URLconf配置
SimpleTestCase.``urls
自1.8版起已弃用:请改用URLconf配置@override_settings(ROOT_URLCONF=...)
。
如果您的应用程序提供了视图,您可能需要包括使用测试客户端来执行这些视图的测试。但是,最终用户可以在自己选择的任何URL上自由地在应用程序中部署视图。
为了为测试提供可靠的URL空间,django.test.*TestCase
类提供了在测试套件执行期间自定义URLconf配置的功能。如果您的*TestCase
实例定义了urls
属性,则*TestCase
将使用该属性的值作为ROOT_URLCONF
例如:
from django.test import TestCase
class TestMyViews(TestCase):
urls = 'myapp.test_urls'
def testIndexPageView(self):
# Here you'd test your view using ``Client``.
call_some_test_code()
此测试用例将使用myapp.test_urls
的内容作为测试用例持续时间的URLconf。
多数据库支持
TransactionTestCase.``multi_db
Django设置与设置文件中的DATABASES
定义中定义的每个数据库对应的测试数据库。但是,运行Django TestCase所需的大部分时间由调用flush
消耗,确保在每次测试运行开始时都有一个干净的数据库。如果您有多个数据库,则需要多次刷新(每个数据库一次),这可能是一个耗时的活动 - 特别是如果您的测试不需要测试多数据库活动。
作为优化,Django仅在每次测试运行开始时刷新default
数据库。如果您的设置包含多个数据库,并且测试需要每个数据库都是干净的,则可以使用测试套件上的multi_db
属性请求完全刷新。
例如:
class TestMyViews(TestCase):
multi_db = True
def testIndexPageView(self):
call_some_test_code()
此测试用例将在运行testIndexPageView
之前刷新所有测试数据库。
multi_db
标志还影响attr:<cite>TransactionTestCase.fixtures</cite>加载到哪些数据库中。默认情况下(当multi_db=False
时),fixture仅加载到default
数据库中。如果multi_db=True
,fixture将加载到所有数据库中。
覆盖设置
警告
使用以下功能临时更改测试中的设置值。不要直接操作django.conf.settings
,因为Django在这种操作后不会恢复原始值。
SimpleTestCase.``settings
()
为了测试目的,在运行测试代码之后临时更改设置并恢复为原始值通常很有用。对于这种用例,Django提供了一个标准的Python上下文管理器(参见 PEP 343),名为settings()
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
块中的代码的LOGIN_URL
设置,然后将其值重置为之前的状态。
SimpleTestCase.``modify_settings
()
New in Django 1.7.
它可以证明难以重新定义包含值列表的设置。在实践中,添加或删除值通常就足够了。modify_settings()
上下文管理器使其变得容易:
from django.test import TestCase
class MiddlewareTestCase(TestCase):
def test_cache_middleware(self):
with self.modify_settings(MIDDLEWARE_CLASSES={
'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/')
Changed in Django 1.7:
以前,override_settings
是从django.test.utils
导入的。
modify_settings
()
New in Django 1.7.
同样,Django提供modify_settings()
装饰器:
from django.test import TestCase, modify_settings
class MiddlewareTestCase(TestCase):
@modify_settings(MIDDLEWARE_CLASSES={
'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_CLASSES={
'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()
可能不会做你期望做的。
我们不建议更改DATABASES
设置。改变CACHES
设置是可能的,但如果你使用使用缓存的内部,如django.contrib.sessions
,有点棘手。例如,您必须在使用缓存会话并覆盖CACHES
的测试中重新初始化会话后端。
最后,避免将您的设置作为模块级常量别名,因为override_settings()
将不适用于这些值,因为它们仅在首次导入模块时进行评估。
您还可以在设置被覆盖后通过删除设置来模拟缺少设置,如下所示:
@override_settings()
def test_something(self):
del settings.LOGIN_URL
...
Changed in Django 1.7:
以前,您只能模拟删除显式覆盖的设置。
覆盖设置时,请确保处理您的应用代码使用缓存或类似功能的情况,即使设置更改也保留状态。Django提供了django.test.signals.setting_changed
信号,允许您注册回调以清除设置,否则在更改设置时重置状态。
Django本身使用这个信号来重置各种数据:
Overridden settings | Data reset |
---|---|
USE_TZ, TIME_ZONE | Databases timezone |
TEMPLATES | Template engines |
SERIALIZATION_MODULES | Serializers cache |
LOCALE_PATHS, LANGUAGE_CODE | Default translation and loaded translations |
MEDIA_ROOT, DEFAULT_FILE_STORAGE | Default file storage |
清空测试发件箱
如果您使用任何Django的自定义TestCase
类,测试运行器将在每个测试用例开始时清除测试电子邮件发件箱的内容。
有关测试期间电子邮件服务的详细信息,请参阅下面的电子邮件服务。
断言
作为Python的正常unittest.
TestCase类实现诸如assertTrue()
和assertEqual()
之类的断言方法,Django的自定义TestCase
类提供了许多有用的自定义断言方法用于测试Web应用程序:
大多数断言方法给出的失败消息可以使用msg_prefix
参数定制。此字符串将作为断言生成的任何失败消息的前缀。这允许您提供其他详细信息,以帮助您确定测试套件中的故障位置和原因。
SimpleTestCase.``assertRaisesMessage
(expected_exception, expected_message, callable_obj=None, *args, **kwargs)
认为执行可调用callable_obj
引发了expected_exception
异常,并且此异常具有expected_message
表示。任何其他结果报告为失败。类似于unittest的assertRaisesRegex()
,区别在于expected_message
不是正则表达式。
SimpleTestCase.``assertFieldOutput
(fieldclass, valid, invalid, field_args=None, field_kwargs=None, empty_value=’’)
断言表单字段在各种输入中正确运行。
Parameters:
- fieldclass - 要测试的字段的类。
- valid - 将有效输入映射到其预期清除值的字典。
- invalid - 将无效输入映射到一个或多个引发的错误消息的字典。
- field_args - 传递给实例化字段的arg。
- 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
,并且该文本
显示在响应的内容中。如果提供了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'
。
New in Django 1.8:
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=’’)
Asserts that the template with the given name was not used in rendering the response.
您可以使用与assertTemplateUsed()
相同的方式作为上下文管理器。
SimpleTestCase.``assertRedirects
(response, expected_url, status_code=302, target_status_code=200, host=None, msg_prefix=’’, fetch_redirect_response=True)
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
.
如果您的请求使用follow
参数,则expected_url
和target_status_code
将是重定向链最后一点的网址和状态代码。
如果expected_url
不包含一个(例如"/bar/"
),host
如果expected_url
是包含主机的绝对网址(例如"http://testhost/bar/"
),则host
忽略。请注意,测试客户端不支持获取外部URL,但是如果您使用自定义HTTP主机进行测试(例如,使用Client(HTTP_HOST="testhost")
New in Django 1.7.
如果fetch_redirect_response
为False
,则不会加载最后一页。由于测试客户端无法获取外部网址,因此如果expected_url
不是您的Django应用程序的一部分,则此功能特别有用。
New in Django 1.7.
在两个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
,即使两个字符串都相同。
出错时的输出可以用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)
New in Django 1.8.
断言JSON片段raw
和expected_data
的不是相等。有关详细信息,请参见assertJSONEqual()
。
出错时的输出可以用msg
参数定制。
TransactionTestCase.``assertQuerysetEqual
(qs, values, transform=repr, ordered=True, msg=None)
断言查询集qs
返回值values
。
使用函数transform
执行qs
和values
的内容的比较;默认情况下,这意味着比较每个值的repr()
。如果repr()
未提供唯一或有帮助的比较,则可以使用任何其他可调用项。
默认情况下,比较也依赖于顺序。如果qs
不提供隐式排序,可以将ordered
参数设置为False
,将比较结果转换为collections.
计数器比较。如果顺序未定义(如果给定的qs
不是有序的,并且比较是针对多个有序值),则会引发ValueError
。
出错时的输出可以用msg
参数定制。
Changed in Django 1.7:
该方法现在接受msg
TransactionTestCase.``assertNumQueries
(num, func, *args, **kwargs)
断言当使用*args
和**kwargs
调用func
时,将执行num
数据库查询。
如果kwargs
中存在"using"
键,它将用作要检查查询数的数据库别名。如果你想用using
参数调用一个函数,你可以通过用lambda
包装调用来添加一个额外的参数:
self.assertNumQueries(7, lambda: my_function(using=7))
您还可以将其用作上下文管理器:
with self.assertNumQueries(2):
Person.objects.create(name="Aaron")
Person.objects.create(name="Daniel")
电子邮件服务
如果您的任何Django视图使用Django’s email functionality发送电子邮件,您可能不希望在每次使用该视图运行测试时发送电子邮件。因此,Django的测试运行器会自动将所有Django发送的电子邮件重定向到一个虚拟发件箱。这使您可以测试发送电子邮件的每个方面 - 从发送到每封邮件内容的邮件数量,而不实际发送邮件。
测试运行器通过用测试后端透明地替换普通电子邮件后端来实现这一点。(不要担心,这对Django之外的任何其他电子邮件发件人(例如您计算机的邮件服务器)(如果您正在运行)没有任何影响。)
django.core.mail.``outbox
在测试运行期间,每封发出的电子邮件都保存在django.core.mail.outbox
中。这是所有已发送的EmailMessage
实例的简单列表。outbox
属性是在使用locmem
电子邮件后端时仅在创建的特殊属性。它通常不作为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')
如前所述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 TestCase
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
类。
skipIfDBFeature
(*feature_name_strings)
如果支持所有命名的数据库功能,请跳过装饰测试或TestCase
。
例如,如果数据库支持事务(例如,不在PostgreSQL下运行,但在MySQL with MyISAM表下),则不会执行以下测试:
class MyTests(TestCase):
@skipIfDBFeature('supports_transactions')
def test_transaction_behavior(self):
# ... conditional test code
Changed in Django 1.7:
skipIfDBFeature
现在可以用来装饰TestCase
类。
Changed in Django 1.8:
skipIfDBFeature
可以接受多个要素字符串。
skipUnlessDBFeature
(*feature_name_strings)
如果任何命名的数据库功能不支持,请跳过装饰测试或TestCase
。
例如,如果数据库支持事务(例如,它将在PostgreSQL下运行,但在MySQL和MyISAM表下的不是),则将执行以下测试:
class MyTests(TestCase):
@skipUnlessDBFeature('supports_transactions')
def test_transaction_behavior(self):
# ... conditional test code
Changed in Django 1.7:
skipUnlessDBFeature
现在可用于装饰TestCase
类。
Changed in Django 1.8:
skipUnlessDBFeature
可以接受多个要素字符串。