Django提供了一组在编写测试时很有用的工具。
测试客户端是一个充当虚拟Web浏览器的Python类,允许您以编程方式测试您的视图并与Django支持的应用程序交互。
您可以对测试客户机执行以下操作:
在URL上模拟get和post请求,并观察响应——从低级HTTP(结果头和状态代码)到页面内容的所有内容。
查看重定向链(如果有),并在每个步骤中检查URL和状态代码。
测试给定请求是否由给定的django模板呈现,模板上下文包含特定值。
注意,测试客户机并不打算替代 Selenium 或其他“浏览器内”框架。Django的测试客户机有不同的关注点。简而言之:
使用Django的测试客户机来确定正在呈现正确的模板,并传递正确的上下文数据。
使用 RequestFactory
直接测试视图功能,绕过路由层和中间件层。
使用浏览器内框架,如 Selenium 为了测试 rendered 超文本标记语言和 behavior 网页的特性,也就是脚本功能。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...'
如本例所示,您可以实例化 Client
在Python交互式解释器的会话中。
注意一些关于测试客户机如何工作的重要事项:
测试客户端执行以下操作 not 要求Web服务器正在运行。事实上,在根本不运行Web服务器的情况下,它将运行得很好!这是因为它避免了HTTP的开销,直接处理Django框架。这有助于快速运行单元测试。
在检索页面时,请记住指定 path URL,而不是整个域。例如,这是正确的:
>>> c.get("/login/")
这是不正确的:
>>> c.get("https://www.example.com/login/")
测试客户端无法检索未由您的Django项目提供支持的网页。如果需要检索其他网页,请使用如下所示的Python标准库模块 urllib
。
为了解析URL,测试客户机使用您的 ROOT_URLCONF
设置。
尽管上面的示例可以在Python交互式解释器中使用,但是测试客户机的一些功能,特别是与模板相关的功能,仅可用。 测试运行时 .
原因是Django的测试运行程序执行了一些黑色魔术,以确定给定视图加载了哪个模板。这种黑色魔法(本质上是Django的模板系统在内存中的补丁)只在测试运行期间发生。
默认情况下,测试客户端将禁用站点执行的任何CSRF检查。
如果,出于某种原因,你 want 要执行CSRF检查的测试客户端,您可以创建强制执行CSRF检查的测试客户端的实例。要执行此操作,请将 enforce_csrf_checks
在构造客户端时使用参数:
>>> from django.test import Client
>>> csrf_client = Client(enforce_csrf_checks=True)
使用 django.test.Client
类以发出请求。
一个测试的HTTP客户端。采用几个可以自定义行为的参数。
headers
允许您指定将随每个请求一起发送的默认标头。例如,要设置一个 User-Agent
标题::
client = Client(headers={"user-agent": "curl/7.79.1"})
query_params
允许您指定将在每个请求上设置的默认查询字符串。
中的任意关键字参数 **defaults
设置WSGI environ variables 。例如,要设置脚本名称::
client = Client(SCRIPT_NAME="/app/")
备注
开头的关键字参数 HTTP_
前缀被设置为标头,但 headers
参数应优先用于可读性。
中的值 headers
, query_params
,以及 extra
传递给的关键字参数 get()
, post()
等优先于传递给类构造函数的默认值。
这个 enforce_csrf_checks
参数可用于测试CSRF保护(见上文)。
这个 raise_request_exception
参数允许控制请求期间引发的异常是否也应该在测试中引发。默认为 True
。
这个 json_encoder
参数允许为中描述的JSON序列化设置自定义JSON编码器。 post()
.
这个 query_params
添加了参数。
一旦你拥有了 Client
实例中,可以调用以下任何方法:
在提供的上发出获取请求 path
返回一个 Response
对象,见下文。
中的键-值对 query_params
字典用于设置查询字符串。例如:
>>> c = Client()
>>> c.get("/customers/details/", query_params={"name": "fred", "age": 7})
...将导致GET请求的评估结果相当于:
/customers/details/?name=fred&age=7
也可以将这些参数传递到 data
参数。然而, query_params
是首选的,因为它适用于任何HTTP方法。
这个 headers
参数可用于指定要在请求中发送的标头。例如:
>>> c = Client()
>>> c.get(
... "/customers/details/",
... query_params={"name": "fred", "age": 7},
... headers={"accept": "application/json"},
... )
...将发送HTTP标头 HTTP_ACCEPT
到详细信息视图,这是测试使用 django.http.HttpRequest.accepts()
方法。
任意关键字参数集WSGI environ variables 。例如,用于设置脚本名称的Header:
>>> c = Client()
>>> c.get("/", SCRIPT_NAME="/app/")
如果您已经有URL编码形式的GET参数,则可以使用该编码而不是使用Data参数。例如,前一个GET请求也可以被伪装成:
>>> c = Client()
>>> c.get("/customers/details/?name=fred&age=7")
如果您提供的URL同时具有编码的GET数据和QUERY_PARAMS或DATA参数,则这些参数将优先。
如果你设置 follow
到 True
客户端将遵循任何重定向和 redirect_chain
属性将在包含中间URL和状态代码的元组的响应对象中设置。
如果你有一个URL /redirect_me/
重定向到 /next/
,重定向到 /final/
,这就是你会看到的:
>>> response = c.get("/redirect_me/", follow=True)
>>> response.redirect_chain
[('http://testserver/next/', 302), ('http://testserver/final/', 302)]
如果你设置 secure
到 True
客户端将模拟一个HTTPS请求。
这个 query_params
添加了参数。
在提供的 path
返回一个 Response
对象,见下文。
中的键-值对 data
词典用于提交帖子数据。例如:
>>> c = Client()
>>> c.post("/login/", {"name": "fred", "passwd": "secret"})
...将导致对此URL的POST请求进行评估:
/login/
...使用此帖子数据:
name=fred&passwd=secret
如果你提供 content_type
作为 application/json , the data
使用序列化 json.dumps()
如果是dict、list或tuple。使用执行序列化 DjangoJSONEncoder
默认情况下,可以通过提供 json_encoder
参数 Client
. 此序列化也发生在 put()
, patch()
和 delete()
请求。
如果你提供任何其他 content_type
(例如) text/xml 对于XML负载),的内容 data
按POST请求中的原样发送,使用 content_type
在HTTP中 Content-Type
标题。
如果您不提供 content_type
中的值 data
将以内容类型 multipart/form-data . 在这种情况下,键值对 data
将被编码为多部分消息并用于创建Post数据有效负载。
为给定的键提交多个值——例如,为 <select multiple>
--将值作为所需键的列表或元组提供。例如,此值为 data
将为名为的字段提交三个选定值 choices
::
{"choices": ["a", "b", "d"]}
提交文件是一种特例。要发布文件,只需提供文件字段名作为键,以及要上载的文件的文件句柄作为值。例如,如果您的表单包含字段 name
和 attachment
,后者是 FileField
:
>>> c = Client()
>>> with open("wishlist.doc", "rb") as fp:
... c.post("/customers/wishes/", {"name": "fred", "attachment": fp})
...
您还可以提供任何类似文件的对象(例如, StringIO
或 BytesIO
)作为文件句柄。如果您要上载到 ImageField
,该对象需要一个 name
属性传递给 validate_image_file_extension
验证器。例如:
>>> from io import BytesIO
>>> img = BytesIO(
... b"GIF89a\x01\x00\x01\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00"
... b"\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x01\x00\x00"
... )
>>> img.name = "myimage.gif"
请注意,如果要对多个文件使用相同的文件句柄 post()
然后调用,您将需要手动重置日志之间的文件指针。最简单的方法是在将文件提供给 post()
如上所示。
您还应该确保以允许读取数据的方式打开文件。如果文件包含二进制数据(如图像),这意味着您需要在 rb
(读二进制)模式。
这个 headers
, query_params
,以及 extra
参数的作用与 Client.get()
。
如果您使用POST请求的URL包含编码参数,则这些参数将在请求.GET数据中可用。例如,如果您要提出以下请求:
>>> c.post(
... "/login/", {"name": "fred", "passwd": "secret"}, query_params={"visitor": "true"}
... )
…处理此请求的视图可以询问request.post以检索用户名和密码,还可以询问request.get以确定用户是否是访问者。
如果你设置 follow
到 True
客户端将遵循任何重定向和 redirect_chain
属性将在包含中间URL和状态代码的元组的响应对象中设置。
如果你设置 secure
到 True
客户端将模拟一个HTTPS请求。
这个 query_params
添加了参数。
在提供的 path
并返回一个 Response
对象。此方法的工作原理与 Client.get()
,包括 follow
, secure
, headers
, query_params
,以及 extra
参数,除非它不返回消息体。
这个 query_params
添加了参数。
在提供的 path
返回一个 Response
对象。用于测试RESTful接口。
什么时候? data
提供,它用作请求主体,以及 Content-Type
标题设置为 content_type
.
这个 follow
, secure
, headers
, query_params
,以及 extra
参数的作用与 Client.get()
。
这个 query_params
添加了参数。
对提供的进行Put请求 path
返回一个 Response
对象。用于测试RESTful接口。
什么时候? data
提供,它用作请求主体,以及 Content-Type
标题设置为 content_type
.
这个 follow
, secure
, headers
, query_params
,以及 extra
参数的作用与 Client.get()
。
这个 query_params
添加了参数。
对提供的进行修补请求 path
返回一个 Response
对象。用于测试RESTful接口。
这个 follow
, secure
, headers
, query_params
,以及 extra
参数的作用与 Client.get()
。
这个 query_params
添加了参数。
在提供的上发出删除请求 path
返回一个 Response
对象。用于测试RESTful接口。
什么时候? data
提供,它用作请求主体,以及 Content-Type
标题设置为 content_type
.
这个 follow
, secure
, headers
, query_params
,以及 extra
参数的作用与 Client.get()
。
这个 query_params
添加了参数。
在提供的上发出跟踪请求 path
返回一个 Response
对象。用于模拟诊断探针。
与其他请求方法不同, data
不作为关键字参数提供,以符合 RFC 9110#section-9.3.8 ,它要求跟踪请求不能有正文。
这个 follow
, secure
, headers
, query_params
,以及 extra
参数的作用与 Client.get()
。
这个 query_params
添加了参数。
Asynchronous version : alogin()
如果你的网站使用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()
收益率 True
如果已接受凭据,则登录成功。
最后,在使用此方法之前,您需要记住创建用户帐户。如上所述,测试运行程序是使用一个测试数据库执行的,默认情况下该数据库不包含任何用户。因此,在您的生产站点上有效的用户帐户将无法在测试条件下工作。您需要创建用户作为测试套件的一部分——手动(使用Django模型API)或使用测试夹具。请记住,如果希望测试用户具有密码,则不能通过直接设置password属性来设置用户的密码--必须使用 set_password()
函数来存储正确散列的密码。或者,您可以使用 create_user()
帮助器方法,用于创建具有正确哈希密码的新用户。
Asynchronous version : aforce_login()
如果你的网站使用django的 authentication system ,您可以使用 force_login()
方法来模拟用户登录站点的效果。使用此方法而不是 login()
当测试要求用户登录时,用户登录方式的详细信息并不重要。
不像 login()
,此方法跳过身份验证和验证步骤:非活动用户 (is_active=False
)允许登录,不需要提供用户凭证。
用户将拥有 backend
属性设置为 backend
参数(应该是点式python路径字符串),或者 settings.AUTHENTICATION_BACKENDS[0]
如果未提供值。这个 authenticate()
函数由调用 login()
通常这样注释用户。
这个方法比 login()
因为昂贵的密码散列算法被绕过。另外,你可以加速 login()
通过 using a weaker hasher while testing .
Asynchronous version : alogout()
如果你的网站使用django的 authentication system , the logout()
方法可用于模拟用户注销网站的效果。
调用此方法后,测试客户机将把所有cookie和会话数据清除为默认值。后续请求似乎来自 AnonymousUser
.
这个 get()
和 post()
方法都返回 Response
对象。这个 Response
对象是 not 一样 HttpResponse
Django视图返回的对象;测试响应对象有一些额外的数据,这些数据对测试代码的验证很有用。
具体地说,A Response
对象具有以下属性:
用于发出导致响应的请求的测试客户端。
响应的主体,如字节串。这是视图呈现的最终页面内容或任何错误消息。
模板 Context
用于呈现生成响应内容的模板的实例。
如果呈现的页面使用了多个模板,则 context
将是一个列表 Context
对象,按渲染顺序。
无论呈现期间使用的模板数量如何,都可以使用 []
接线员。例如,上下文变量 name
可以使用以下命令检索:
>>> response = client.get("/foo/")
>>> response.context["name"]
'Arthur'
不使用django模板?
仅当使用 DjangoTemplates
后端。如果您正在使用另一个模板引擎, context_data
对于具有该属性的响应,可能是一个合适的替代方法。
由三个值组成的二元组,提供有关视图期间发生的未处理异常(如果有的话)的信息。
值是(类型、值、追溯),与Python返回的相同 sys.exc_info()
.它们的含义是:
type :异常的类型。
value :异常实例。
traceback :一个追溯对象,它封装异常最初发生的点处的调用堆栈。
如果没有发生异常,那么 exc_info
将会是 None
。
响应的正文,解析为JSON。将额外的关键字参数传递给 json.loads()
。例如:
>>> response = client.get("/foo/")
>>> response.json()["name"]
'Arthur'
如果 Content-Type
页眉不是 "application/json"
然后 ValueError
在尝试分析响应时将引发。
刺激响应的请求数据。
这个 WSGIRequest
由生成响应的测试处理程序生成的实例。
响应的HTTP状态,以整数表示。有关已定义代码的完整列表,请参见 IANA status code registry .
列表 Template
用于按呈现顺序呈现最终内容的实例。对于列表中的每个模板,使用 template.name
如果模板是从文件加载的,则获取模板的文件名。(名称是一个字符串,例如 'admin/index.html'
)
不使用django模板?
仅当使用 DjangoTemplates
后端。如果您正在使用另一个模板引擎, template_name
如果只需要用于渲染的模板的名称,则可能是一个合适的替代方法。
的实例 ResolverMatch
为了回应。你可以使用 func
例如,用于验证提供响应的视图的属性:
# my_view here is a function based view.
self.assertEqual(response.resolver_match.func, my_view)
# Class-based views need to compare the view_class, as the
# functions generated by as_view() won't be equal.
self.assertIs(response.resolver_match.func.view_class, MyView)
如果找不到给定的URL,访问此属性将引发 Resolver404
例外。
与正常响应一样,您还可以通过访问标题 HttpResponse.headers
.例如,您可以使用确定响应的内容类型 response.headers['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将存储在测试客户机中,并与所有后续的cookie一起发送 get()
和 post()
请求。
不遵守这些cookie的过期策略。如果希望cookie过期,请手动删除它或创建一个新的 Client
实例(将有效删除所有cookie)。
测试客户端具有存储持久状态信息的属性。可以将这些属性作为测试条件的一部分进行访问。
Python SimpleCookie
对象,包含所有客户端cookie的当前值。参见 http.cookies
更多模块。
包含会话信息的类似字典的对象。见 session documentation 详细信息。
要修改会话并保存它,必须首先将其存储在变量中(因为 SessionStore
每次访问此属性时创建)::
def test_something(self):
session = self.client.session
session["somekey"] = "test"
session.save()
当测试支持国际化和本地化的应用程序时,您可能希望为测试客户机请求设置语言。这样做的方法取决于 LocaleMiddleware
启用。
如果启用了中间件,则可以通过创建名为的cookie来设置语言。 LANGUAGE_COOKIE_NAME
语言代码的值:
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("/", headers={"accept-language": "fr"})
self.assertEqual(response.content, b"Bienvenue sur mon site.")
备注
使用这些方法时,请确保在每次测试结束时重置活动语言:
def tearDown(self):
translation.activate(settings.LANGUAGE_CODE)
更多详细信息 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提供了这个基类的一些扩展:
Django单元测试类的层次结构¶
你可以转换正常 unittest.TestCase
到任何子类:将测试的基本类更改为 unittest.TestCase
到子类别。所有标准Python单元测试功能都将可用,并且将通过下面各部分所述的一些有用的添加来增强它。
SimpleTestCase
¶一个子类 unittest.TestCase
添加了此功能:
一些有用的断言,如:
检查是否可调用 raises a certain exception
.
检查是否可调用 triggers a certain warning
.
测试表单字段 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
.
验证这两个 URLs
检验HTTP redirect
是由应用程序执行的。
坚固的测试二 HTML fragments
对于平等/不平等或 containment
.
坚固的测试二 XML fragments
平等/不平等。
坚固的测试二 JSON fragments
为了平等。
运行测试的能力 modified settings .
如果测试进行任何数据库查询,请使用子类 TransactionTestCase
或 TestCase
.
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()
. 如果发生这种情况,无论是类中的测试还是 tearDownClass()
运行。在情况下 django.test.TestCase
,这将泄漏在中创建的事务 super()
这导致了各种症状,包括一些平台上的分段故障(在MacOS上报告)。如果你想故意提出一个例外,比如 unittest.SkipTest
在里面 setUpClass()
,在调用之前一定要做 super()
为了避免这种情况。
TransactionTestCase
¶TransactionTestCase
继承自 SimpleTestCase
要添加一些特定于数据库的功能:
贾戈 TestCase
类是更常用的子类 TransactionTestCase
它利用数据库事务工具来加快在每次测试开始时将数据库重置为已知状态的过程。然而,这样做的结果是,一些数据库行为不能在Django中进行测试 TestCase
类。例如,您不能测试一个代码块是否在事务中执行,这在使用时是必需的。 select_for_update()
. 在这种情况下,您应该使用 TransactionTestCase
.
TransactionTestCase
和 TestCase
除了将数据库重置为已知状态的方式以及测试代码测试提交和回滚效果的能力外,都是相同的:
A TransactionTestCase
通过截断所有表,在测试运行后重置数据库。一 TransactionTestCase
可以调用commit和rollback并观察这些调用对数据库的影响。
A TestCase
另一方面,在测试后不截断表。相反,它将测试代码包含在一个在测试结束时回滚的数据库事务中。这保证了测试结束时的回滚会将数据库恢复到初始状态。
警告
TestCase
在不支持回滚的数据库上运行(例如带有myisam存储引擎的mysql),以及 TransactionTestCase
,将通过从测试数据库中删除所有数据在测试结束时回滚。
应用程序 will not see their data reloaded ;如果您需要此功能(例如,第三方应用程序应启用此功能),可以设置 serialized_rollback = True
里面 TestCase
身体。
TestCase
¶这是在Django中用于编写测试的最常见类。它继承自 TransactionTestCase
(延期) SimpleTestCase
)如果Django应用程序不使用数据库,请使用 SimpleTestCase
.
类:
将测试包装在两个嵌套的 atomic()
块:一个用于整个类,一个用于每个测试。因此,如果要测试某些特定的数据库事务行为,请使用 TransactionTestCase
.
在每个测试结束时检查可延迟的数据库约束。
它还提供了一种附加方法:
类级别 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()
必须支持使用创建深层拷贝 copy.deepcopy()
以便将它们与由每种测试方法执行的更改隔离。
返回捕获的上下文管理器 transaction.on_commit()
给定数据库连接的回调。退出上下文时,它返回一个列表,其中包含捕获的回调函数。从此列表中,您可以对回调进行断言或调用它们来调用它们的副作用,从而模拟提交。
using
是要捕获回调的数据库连接的别名。
如果 execute
是 True
,如果没有发生异常,所有回调都将在上下文管理器退出时被调用。这模拟了包装代码块之后的提交。
例如::
from django.core import mail
from django.test import TestCase
class ContactTests(TestCase):
def test_post(self):
with self.captureOnCommitCallbacks(execute=True) as callbacks:
response = self.client.post(
"/contact/",
{"message": "I like your site"},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(callbacks), 1)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, "Contact Form")
self.assertEqual(mail.outbox[0].body, "I like your site")
LiveServerTestCase
¶LiveServerTestCase
基本上与 TransactionTestCase
有一个额外的特性:它在安装时在后台启动一个动态的Django服务器,然后在拆卸时关闭它。这允许使用除 Django dummy client 例如, Selenium 客户端,在浏览器中执行一系列功能测试并模拟实际用户的操作。
实时服务器监听 localhost
并绑定到端口0,该端口使用操作系统分配的空闲端口。可以使用访问服务器的URL self.live_server_url
在测试过程中。
演示如何使用 LiveServerTestCase
,让我们编写一个硒测试。首先,您需要安装 selenium 套餐:
$ python -m pip install "selenium >= 4.8.0"
...\> py -m pip install "selenium >= 4.8.0"
然后,添加一个 LiveServerTestCase
-应用程序测试模块的基础测试(例如: myapp/tests.py
)对于这个例子,我们假设您正在使用 staticfiles
应用程序并希望在测试执行期间提供与开发时类似的静态文件 DEBUG=True
也就是说,不必使用 collectstatic
. 我们将使用 StaticLiveServerTestCase
提供该功能的子类。用它代替 django.test.LiveServerTestCase
如果你不需要的话。
此测试的代码如下:
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium.webdriver.common.by import By
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(f"{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
...\> manage.py test myapp.tests.MySeleniumTests.test_login
此示例将自动打开Firefox,然后转到登录页面,输入凭据并按“登录”按钮。Selenium提供其他驱动程序,以防您没有安装或希望使用其他浏览器。上面的示例只是Selenium客户机所能做的一小部分;请查看 full reference 了解更多详细信息。
备注
当使用内存中的sqlite数据库运行测试时,同一数据库连接将由两个并行线程共享:运行活动服务器的线程和运行测试用例的线程。重要的是要防止两个线程通过这个共享连接同时进行数据库查询,因为这有时会随机导致测试失败。因此,您需要确保两个线程不会同时访问数据库。特别是,这意味着在某些情况下(例如,在单击链接或提交表单之后),您可能需要检查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 FAQ 和 Selenium documentation 以获取更多信息。
中的每个测试用例 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)
如果你想用不同的 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()
如果数据库中没有任何数据,那么数据库支持的网站的测试用例类就没有多大用处。测试更具可读性,使用ORM创建对象也更易于维护,例如在 TestCase.setUpTestData()
但是,您也可以使用 fixtures 。
夹具是Django知道如何导入数据库的数据集合。例如,如果您的站点有用户帐户,您可以设置一个伪造用户帐户的装置,以便在测试期间填充数据库。
创建夹具的最简单方法是使用 manage.py dumpdata
命令。这假设您的数据库中已经有一些数据。见 dumpdata documentation
了解更多详细信息。
一旦创建了一个装置并将其放置在 fixtures
目录在您的 INSTALLED_APPS
,可以通过指定 fixtures
你的类属性 django.test.TestCase
子类::
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将刷新数据库,并将数据库返回到它在运行后的状态。 migrate
被叫来。
然后,安装所有命名的装置。在本例中,Django将安装任何名为 mammals
,后跟任何名为 birds
。请参阅 夹具 主题,了解有关定义和安装夹具的更多详细信息。
出于性能原因, TestCase
为整个测试类加载一次fixture,在 setUpTestData()
而不是在每次测试之前,它使用事务在每次测试之前清理数据库。在任何情况下,您都可以确定测试的结果不会受到其他测试或测试执行顺序的影响。
默认情况下,设备仅加载到 default
数据库。如果使用多个数据库并设置 TransactionTestCase.databases
,设备将加载到所有指定的数据库中。
如果应用程序提供视图,您可能希望包括使用测试客户机来运行这些视图的测试。但是,最终用户可以自由地将视图部署到应用程序中他们选择的任何URL。这意味着您的测试不能依赖于这样一个事实:您的视图将在特定的URL上可用。用装饰你的测试类或测试方法 @override_settings(ROOT_URLCONF=...)
用于URLCONF配置。
Django建立了一个测试数据库,对应于中定义的每个数据库 DATABASES
在您的设置中定义并由至少一个测试引用到 databases
.
然而,经营一家Django的大部分时间 TestCase
被调用消耗 flush
这样可以确保在每次测试运行开始时都有一个干净的数据库。如果您有多个数据库,则需要多次刷新(每个数据库一次),这可能是一个耗时的活动——特别是当您的测试不需要测试多个数据库活动时。
作为优化,Django只刷新 default
每次测试运行开始时的数据库。如果安装程序包含多个数据库,并且有一个测试要求每个数据库都是干净的,则可以使用 databases
用于请求刷新额外数据库的测试套件属性。
例如::
class TestMyViews(TransactionTestCase):
databases = {"default", "other"}
def test_index_page_view(self):
call_some_test_code()
此测试用例类将刷新 default
和 other
在运行之前测试数据库 test_index_page_view
。您还可以使用 '__all__'
以指定必须刷新所有测试数据库。
这个 databases
标志还控制 TransactionTestCase.fixtures
加载到。默认情况下,设备仅加载到 default
数据库。
对不在中的数据库的查询 databases
将给出断言错误以防止测试之间的状态泄漏。
默认情况下,只有 default
数据库将在 TestCase
执行和尝试查询其他数据库将导致断言错误,以防止测试之间的状态泄漏。
使用 databases
测试类的class属性,用于请求对非``default``数据库进行事务包装。
例如::
class OtherDBTests(TestCase):
databases = {"other"}
def test_other_db_query(self): ...
此测试将只允许对 other
数据库。就像 SimpleTestCase.databases
和 TransactionTestCase.databases
, the '__all__'
常量可用于指定测试应允许查询所有数据库。
警告
使用下面的函数临时更改测试中设置的值。不要操纵 django.conf.settings
直接因为django不会在这样的操作之后恢复原始值。
出于测试目的,在运行测试代码后临时更改设置并恢复到原始值通常很有用。对于这个用例,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/")
此示例将重写 LOGIN_URL
中的代码的设置 with
块,然后将其值重置为以前的状态。
事实证明,重新定义包含值列表的设置是很困难的。在实践中,添加或删除值通常就足够了。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
当值不存在时。
如果要重写测试方法的设置,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("/")
# ...
备注
当给定一个类时,这些修饰符直接修改该类并返回它;它们不创建并返回该类的修改副本。因此,如果您尝试调整上述示例,以将返回值分配给与 LoginTestCase
或 MiddlewareTestCase
,您可能会惊讶地发现原来的测试用例类仍然同样受到decorator的影响。对于一个给定的类, 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
...
覆盖设置时,请确保处理应用程序代码使用缓存或类似功能的情况,即使更改了设置,这些功能也会保持状态。Django提供 django.test.signals.setting_changed
当设置更改时,允许注册回调以清除或重置状态的信号。
Django本身使用此信号重置各种数据:
覆盖的设置 |
数据重置 |
---|---|
使用时区 |
数据库时区 |
TEMPLATES |
模板引擎 |
FORM_RENDERER |
默认渲染器 |
SERIALIZATION_MODULES |
序列化程序缓存 |
区域设置路径,语言代码 |
默认翻译和加载的翻译 |
STATIC_ROOT、STATIC_URL、存储 |
存储配置 |
属性时重置默认渲染器 FORM_RENDERER
已添加设置已更改。
将包装上下文中定义的模型注册到它们自己的独立模型中 apps
注册表。在为测试创建模型类时,此功能非常有用,因为这些类将在之后被完全删除,并且不存在名称冲突的风险。
独立注册表应该包含的应用程序标签必须作为单独的参数传递。您可以使用 isolate_apps()
作为一名装饰师或环境经理。例如::
from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps
class MyModelTests(SimpleTestCase):
@isolate_apps("app_label")
def test_model_definition(self):
class TestModel(models.Model):
pass
...
…或:
with isolate_apps("app_label"):
class TestModel(models.Model):
pass
...
装饰符形式也可以应用于类。
可以指定两个可选的关键字参数:
attr_name
:如果用作类修饰符,则分配独立注册表的属性。
kwarg_name
:如果用作函数修饰符,则传递隔离注册表的关键字参数。
临时性的 Apps
用于隔离模型注册的实例在用作类修饰器时可以作为属性检索,方法是使用 attr_name
参数::
@isolate_apps("app_label", attr_name="apps")
class TestModelDefinition(SimpleTestCase):
def test_model_definition(self):
class TestModel(models.Model):
pass
self.assertIs(self.apps.get_model("app_label", "TestModel"), TestModel)
…方法用作方法修饰符时,作为测试方法上的参数。 kwarg_name
参数::
class TestModelDefinition(SimpleTestCase):
@isolate_apps("app_label", kwarg_name="apps")
def test_model_definition(self, apps):
class TestModel(models.Model):
pass
self.assertIs(apps.get_model("app_label", "TestModel"), TestModel)
如果你使用任何Django的习惯 TestCase
类,测试运行程序将在每个测试用例开始时清除测试电子邮件发件箱的内容。
有关测试期间电子邮件服务的更多详细信息,请参阅 Email services 下面。
作为Python的常态 unittest.TestCase
类实现断言方法,如 assertTrue()
和 assertEqual()
,Django的习俗 TestCase
类提供了许多可用于测试Web应用程序的自定义断言方法:
大多数这些断言方法给出的失败消息都可以用 msg_prefix
参数。此字符串将作为断言生成的任何失败消息的前缀。这允许您提供其他详细信息,以帮助您确定测试套件中失败的位置和原因。
断言执行 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.assertRaisesMessage()
但为了 assertWarnsRegex()
而不是 assertRaisesRegex()
.
断言窗体字段在各种输入下的行为正确。
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."]}
)
断言窗体上的某个字段引发提供的错误列表。
form
是一种 Form
举个例子。表格必须是 bound 但不一定经过验证 (assertFormError()
将自动调用 full_clean()
在表格上)。
field
表单上要检查的域的名称。要检查表单的 non-field errors
,使用 field=None
。
errors
是该字段应包含的所有错误字符串的列表。如果您只希望出现一个错误,也可以传递单个错误字符串,这意味着 errors='error message'
与之相同 errors=['error message']
。
断言 formset
在呈现时引发提供的错误列表。
formset
是一种 FormSet
举个例子。必须绑定表单集,但不一定要验证 (assertFormSetError()
将自动调用 full_clean()
在表格集上)。
form_index
中的表单编号。 FormSet
(从0开始)。使用 form_index=None
检查表单集的非表单错误,即调用 formset.non_form_errors()
。在这种情况下,您还必须使用 field=None
。
field
和 errors
与参数的含义相同 assertFormError()
。
断言一个 response
产生了给定的 status_code
那就是 text
出现在其 content
。如果 count
是提供的, text
必须准确地发生 count
回应中的次数。
集合 html
到 True
处理 text
作为HTML。与响应内容的比较将基于HTML语义,而不是逐字符相等。在大多数情况下,空白被忽略,属性排序不重要。见 assertHTMLEqual()
了解更多详细信息。
在旧版本中,错误消息不包含响应内容。
断言一个 response
产生了给定的 status_code
那就是 text
会吗? not 出现在其 content
。
集合 html
到 True
处理 text
作为HTML。与响应内容的比较将基于HTML语义,而不是逐字符相等。在大多数情况下,空白被忽略,属性排序不重要。见 assertHTMLEqual()
了解更多详细信息。
在旧版本中,错误消息不包含响应内容。
断言在呈现响应时使用了具有给定名称的模板。
response
必须是由 test client
。
template_name
应该是一个字符串,如 '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")
断言具有给定名称的模板是 not 用于呈现响应。
您可以使用它作为上下文管理器,方法与 assertTemplateUsed()
.
断言两个URL相同,忽略查询字符串参数的顺序(具有相同名称的参数除外)。例如, /path/?x=1&y=2
等于 /path/?y=2&x=1
,但是 /path/?a=1&a=2
不等于 /path/?a=2&a=1
.
断言 response
返回了一个 status_code
重定向状态,重定向到 expected_url
(包括任何 GET
数据),并且最终页面是与 target_status_code
。
如果您的请求使用 follow
论点 expected_url
和 target_status_code
将是重定向链的最后一点的URL和状态代码。
如果 fetch_redirect_response
是 False
,将不加载最后一页。由于测试客户端无法获取外部URL,因此在 expected_url
不是你的django应用程序的一部分。
在比较两个URL时,正确处理方案。如果在我们重定向到的位置没有指定任何方案,则使用原始请求的方案。如果有,计划 expected_url
是用来比较的。
断言字符串 html1
和 html2
一律平等。比较基于HTML语义。比较考虑了以下因素:
忽略HTML标记前后的空白。
所有类型的空白都被认为是等效的。
所有打开的标记都是隐式关闭的,例如当周围的标记关闭或HTML文档结束时。
空标签等同于它们的自动关闭版本。
HTML元素属性的顺序并不重要。
布尔属性(如 checked
)都等于名称和值相等的属性(请参见示例)。
引用同一字符的文本、字符引用和实体引用是等效的。
以下示例是有效的测试,不会引发任何 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
参数。
断言字符串 html1
和 html2
是 not 相等。比较基于HTML语义。见 assertHTMLEqual()
有关详细信息。
html1
和 html2
必须包含HTML。一个 AssertionError
如果其中一个无法分析,则将引发。
出错时的输出可以用 msg
参数。
断言琴弦 xml1
和 xml2
都是平等的。该比较基于ML语义。类似于 assertHTMLEqual()
,比较是对解析的内容进行的,因此只考虑语义差异,而不是语法差异。当任何参数中传递无效的ML时, AssertionError
即使两个字符串相同,也始终引发。
将忽略HTML声明、文档类型、处理指令和注释。仅比较根元素及其子元素。
出错时的输出可以用 msg
参数。
断言字符串 xml1
和 xml2
是 not 相等。比较基于XML语义。见 assertXMLEqual()
有关详细信息。
出错时的输出可以用 msg
参数。
断言该HTML片段 needle
包含在 haystack
一次。
如果 count
指定整数参数,然后再指定 needle
事件将被严格验证。
大多数情况下会忽略空格,并且属性排序并不重要。看见 assertHTMLEqual()
了解更多详细信息。
在旧版本中,错误消息不包含 haystack
。
断言该HTML片段 needle
是 not 包含在 haystack
。
大多数情况下会忽略空格,并且属性排序并不重要。看见 assertHTMLEqual()
了解更多详细信息。
断言JSON片段 raw
和 expected_data
一律平等。当重量级被委托给 json
类库。
出错时的输出可以用 msg
参数。
断言JSON片段 raw
和 expected_data
是 not 相等。见 assertJSONEqual()
更多详情。
出错时的输出可以用 msg
参数。
断言查询集 qs
匹配特定的值可迭代 values
。
如果 transform
是提供的, values
与应用程序生成的列表进行比较 transform
成员都有拥有 qs
。
默认情况下,比较也是顺序相关的。如果 qs
不提供隐式排序,则可以将 ordered
参数设置为 False
,这将比较变成一种 collections.Counter
比较一下。如果顺序未定义(如果给定的 qs
不是有序的,并且比较的对象是多个有序的值),则 ValueError
都被养大了。
出错时的输出可以用 msg
参数。
断言当 func
被调用 *args
和 **kwargs
那个 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'
.
然后您可以选择要运行的测试。例如,只运行快速测试:
$ ./manage.py test --tag=fast
...\> manage.py test --tag=fast
或者运行快速测试和核心测试(即使速度很慢):
$ ./manage.py test --tag=fast --tag=core
...\> manage.py test --tag=fast --tag=core
您还可以通过标记排除测试。要在不慢的情况下运行核心测试,请执行以下操作:
$ ./manage.py test --tag=core --exclude-tag=slow
...\> manage.py test --tag=core --exclude-tag=slow
test --exclude-tag
优先于 test --tag
,因此,如果一个测试有两个标记,并且您选择其中一个标记并排除另一个标记,则不会运行该测试。
如果您只是想测试同步视图的输出,标准测试客户端将在其自己的同步循环中运行它们,而无需您进行任何额外的工作。
然而,如果您想为Django项目编写全同步测试,则需要考虑几件事。
首先,你的测试必须是 async def
测试类上的方法(以便为它们提供一个同步上下文)。Django将自动检测到任何 async def
测试并包装它们,以便它们在自己的事件循环中运行。
如果您正在从同步函数进行测试,则还必须使用同步测试客户端。这是可用的, django.test.AsyncClient
,或作为 self.async_client
在任何测试中。
AsyncClient
具有与同步(普通)测试客户端相同的方法和签名,但有以下例外:
在初始化中,任意关键字参数 defaults
直接添加到ASGI作用域。
标头作为 extra
关键字参数不应包含 HTTP_
同步客户端所需的前缀(请参见 Client.get()
)。例如,下面是如何设置一个HTTP Accept
标题:
>>> c = AsyncClient()
>>> c.get("/customers/details/", {"name": "fred", "age": 7}, ACCEPT="application/json")
这个 query_params
添加了参数。
vbl.使用 AsyncClient
必须等待任何发出请求的方法::
async def test_my_thing(self):
response = await self.async_client.get("/some-url/")
self.assertEqual(response.status_code, 200)
同步客户端还可以调用同步视图;它通过Django的 asynchronous request path ,这两者都支持。通过调用的任何视图 AsyncClient
会得到一个 ASGIRequest
对象为其 request
而不是 WSGIRequest
普通客户创建的。
警告
如果您正在使用测试装饰器,它们必须与地理位置兼容,以确保它们正确工作。Django的内置装饰器将正常运行,但第三方装饰器可能看起来不执行(它们会“包装”执行流的错误部分,而不是您的测试)。
If you need to use these decorators, then you should decorate your test
methods with async_to_sync()
inside of them instead:
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's email functionality ,您可能不想在每次使用该视图运行测试时发送电子邮件。因此,Django的测试运行程序会自动将所有Django发送的电子邮件重定向到一个虚拟发件箱。这使您可以测试发送电子邮件的各个方面——从发送的消息数量到每个消息的内容——而不必实际发送消息。
测试运行程序通过透明地将普通电子邮件后端替换为测试后端来实现这一点。(不用担心——这对Django以外的任何其他电子邮件发送者都没有影响,例如,如果您运行的是计算机的邮件服务器。)
在测试运行期间,每封发出的电子邮件都保存在 django.core.mail.outbox
.这是所有列表 EmailMessage
已发送的实例。 的 outbox
属性是创建的特殊属性 only 当 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 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", poll_ids=[1], stdout=out)
self.assertIn('Successfully closed poll "1"', out.getvalue())
UnitTest库提供 @skipIf
和 @skipUnless
如果提前知道这些测试在某些条件下会失败,那么修饰符允许您跳过测试。
例如,如果您的测试需要一个特定的可选库才能成功,您可以用 @skipIf
. 然后,测试运行人员将报告没有执行测试以及为什么,而不是失败测试或完全忽略测试。
为了补充这些测试跳过行为,Django提供了两个额外的跳过修饰符。这些修饰符不是测试一般的布尔值,而是检查数据库的功能,如果数据库不支持特定的命名功能,则跳过测试。
装饰符使用字符串标识符来描述数据库功能。此字符串对应于数据库连接要素类的属性。看见 django.db.backends.base.features.BaseDatabaseFeatures class 获取可用作跳过测试的基础的数据库功能的完整列表。
跳过装饰测试或 TestCase
如果支持所有命名数据库功能。
例如,如果数据库支持事务(例如, not 在PostgreSQL下运行,但在带有myisam表的mysql下运行)::
class MyTests(TestCase):
@skipIfDBFeature("supports_transactions")
def test_transaction_behavior(self):
# ... conditional test code
pass
跳过装饰测试或 TestCase
如果任何命名数据库功能 not 支持。
例如,只有当数据库支持事务(例如,它将在PostgreSQL下运行,但是 not 在mysql和myisam表下)::
class MyTests(TestCase):
@skipUnlessDBFeature("supports_transactions")
def test_transaction_behavior(self):
# ... conditional test code
pass
7月 22, 2024