测试工具

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 类以发出请求。

class Client(enforce_csrf_checks=False, raise_request_exception=True, json_encoder=DjangoJSONEncoder, *, headers=None, query_params=None, **defaults)[源代码]

一个测试的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 参数应优先用于可读性。

中的值 headersquery_params ,以及 extra 传递给的关键字参数 get()post() 等优先于传递给类构造函数的默认值。

这个 enforce_csrf_checks 参数可用于测试CSRF保护(见上文)。

这个 raise_request_exception 参数允许控制在请求期间引发的异常是否也应在测试中引发。默认为 True .

这个 json_encoder 参数允许为中描述的JSON序列化设置自定义JSON编码器。 post() .

Changed in Django Development version:

这个 query_params 添加了参数。

一旦你拥有了 Client 实例中,可以调用以下任何方法:

get(path, data=None, follow=False, secure=False, *, headers=None, query_params=None, **extra)[源代码]

在提供的上发出获取请求 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参数,则这些参数将优先。

如果你设置 followTrue 客户端将遵循任何重定向和 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)]

如果你设置 secureTrue 客户端将模拟一个HTTPS请求。

Changed in Django Development version:

这个 query_params 添加了参数。

post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, *, headers=None, query_params=None, **extra)[源代码]

在提供的 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"]}

提交文件是一种特例。要发布文件,只需提供文件字段名作为键,以及要上载的文件的文件句柄作为值。例如,如果您的表单包含字段 nameattachment ,后者是 FileField

>>> c = Client()
>>> with open("wishlist.doc", "rb") as fp:
...     c.post("/customers/wishes/", {"name": "fred", "attachment": fp})
...

您还可以提供任何类似文件的对象(例如, StringIOBytesIO )作为文件句柄。如果您要上载到 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 (读二进制)模式。

这个 headersquery_params ,以及 extra 参数的作用与 Client.get()

如果您使用POST请求的URL包含编码参数,则这些参数将在请求.GET数据中可用。例如,如果您要提出以下请求:

>>> c.post(
...     "/login/", {"name": "fred", "passwd": "secret"}, query_params={"visitor": "true"}
... )

…处理此请求的视图可以询问request.post以检索用户名和密码,还可以询问request.get以确定用户是否是访问者。

如果你设置 followTrue 客户端将遵循任何重定向和 redirect_chain 属性将在包含中间URL和状态代码的元组的响应对象中设置。

如果你设置 secureTrue 客户端将模拟一个HTTPS请求。

Changed in Django Development version:

这个 query_params 添加了参数。

head(path, data=None, follow=False, secure=False, *, headers=None, query_params=None, **extra)[源代码]

在提供的 path 并返回一个 Response 对象。此方法的工作原理与 Client.get() ,包括 followsecureheadersquery_params ,以及 extra 参数,除非它不返回消息体。

Changed in Django Development version:

这个 query_params 添加了参数。

options(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[源代码]

在提供的 path 返回一个 Response 对象。用于测试RESTful接口。

什么时候? data 提供,它用作请求主体,以及 Content-Type 标题设置为 content_type .

这个 followsecureheadersquery_params ,以及 extra 参数的作用与 Client.get()

Changed in Django Development version:

这个 query_params 添加了参数。

put(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[源代码]

对提供的进行Put请求 path 返回一个 Response 对象。用于测试RESTful接口。

什么时候? data 提供,它用作请求主体,以及 Content-Type 标题设置为 content_type .

这个 followsecureheadersquery_params ,以及 extra 参数的作用与 Client.get()

Changed in Django Development version:

这个 query_params 添加了参数。

patch(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[源代码]

对提供的进行修补请求 path 返回一个 Response 对象。用于测试RESTful接口。

这个 followsecureheadersquery_params ,以及 extra 参数的作用与 Client.get()

Changed in Django Development version:

这个 query_params 添加了参数。

delete(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[源代码]

在提供的上发出删除请求 path 返回一个 Response 对象。用于测试RESTful接口。

什么时候? data 提供,它用作请求主体,以及 Content-Type 标题设置为 content_type .

这个 followsecureheadersquery_params ,以及 extra 参数的作用与 Client.get()

Changed in Django Development version:

这个 query_params 添加了参数。

trace(path, follow=False, secure=False, *, headers=None, query_params=None, **extra)[源代码]

在提供的上发出跟踪请求 path 返回一个 Response 对象。用于模拟诊断探针。

与其他请求方法不同, data 不作为关键字参数提供,以符合 RFC 9110#section-9.3.8 ,它要求跟踪请求不能有正文。

这个 followsecureheadersquery_params ,以及 extra 参数的作用与 Client.get()

Changed in Django Development version:

这个 query_params 添加了参数。

login(**credentials)
alogin(**credentials)

Asynchronous versionalogin()

如果你的网站使用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() 帮助器方法,用于创建具有正确哈希密码的新用户。

Changed in Django 5.0:

alogin() 添加了方法。

force_login(user, backend=None)
aforce_login(user, backend=None)

Asynchronous versionaforce_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 .

Changed in Django 5.0:

aforce_login() 添加了方法。

logout()
alogout()

Asynchronous versionalogout()

如果你的网站使用django的 authentication system , the logout() 方法可用于模拟用户注销网站的效果。

调用此方法后,测试客户机将把所有cookie和会话数据清除为默认值。后续请求似乎来自 AnonymousUser .

Changed in Django 5.0:

alogout() 添加了方法。

测试响应

这个 get()post() 方法都返回 Response 对象。这个 Response 对象是 not 一样 HttpResponse Django视图返回的对象;测试响应对象有一些额外的数据,这些数据对测试代码的验证很有用。

具体地说,A Response 对象具有以下属性:

class Response
client

用于发出导致响应的请求的测试客户端。

content

响应的主体,如字节串。这是视图呈现的最终页面内容或任何错误消息。

context

模板 Context 用于呈现生成响应内容的模板的实例。

如果呈现的页面使用了多个模板,则 context 将是一个列表 Context 对象,按渲染顺序。

无论呈现期间使用的模板数量如何,都可以使用 [] 接线员。例如,上下文变量 name 可以使用以下命令检索:

>>> response = client.get("/foo/")
>>> response.context["name"]
'Arthur'

不使用django模板?

仅当使用 DjangoTemplates 后端。如果您正在使用另一个模板引擎, context_data 对于具有该属性的响应,可能是一个合适的替代方法。

exc_info

由三个值组成的元组,提供有关视图期间发生的未处理异常(如果有)的信息。

这些值是(类型、值、回溯),与python返回的值相同。 sys.exc_info() . 它们的含义是:

  • type :异常的类型。

  • 价值 :异常实例。

  • 追溯 :一个回溯对象,在异常最初发生的点封装调用堆栈。

如果没有发生异常,那么 exc_infoNone .

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 status code registry .

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 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_exceptionTrue ,该异常将在测试用例中可见。然后您可以使用标准 try ... except 块或 assertRaises() 测试异常。

测试客户机唯一看不到的异常是 Http404PermissionDeniedSystemExitSuspiciousOperation . Django在内部捕获这些异常并将其转换为适当的HTTP响应代码。在这些情况下,您可以检查 response.status_code 在你的测试中。

如果 Client.raise_request_exceptionFalse ,测试客户端将返回500个响应,就像返回到浏览器一样。响应具有属性 exc_info 提供有关未处理异常的信息。

持续状态

测试客户端是有状态的。如果响应返回一个cookie,那么该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()
Client.asession()
New in Django 5.0.

这类似于 session 属性,但它在异步上下文中工作。

设置语言

当测试支持国际化和本地化的应用程序时,您可能希望为测试客户机请求设置语言。这样做的方法取决于 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单元测试类的层次结构(TestCase子类)

Django单元测试类的层次结构

你可以转换一个法线 unittest.TestCase 对任何子类:将测试的基类从 unittest.TestCase 到子类。所有标准的python单元测试功能都将可用,并将通过一些有用的附加功能进行增强,如下面每个部分所述。

SimpleTestCase

class SimpleTestCase[源代码]

一个子类 unittest.TestCase 添加了此功能:

如果测试进行任何数据库查询,请使用子类 TransactionTestCaseTestCase .

SimpleTestCase.databases

SimpleTestCase 默认情况下不允许数据库查询。这有助于避免执行写查询,这将影响其他测试,因为 SimpleTestCase 测试未在事务中运行。如果您不关心此问题,可以通过设置 databases 类属性到 '__all__' 在你的测试课上。

警告

SimpleTestCase 及其子类(例如 TestCase ……)依靠 setUpClass()tearDownClass() 执行一些类范围的初始化(例如覆盖设置)。如果需要重写这些方法,请不要忘记调用 super 实施::

class MyTestCase(TestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        ...

    @classmethod
    def tearDownClass(cls):
        ...
        super().tearDownClass()

如果在 setUpClass() . 如果发生这种情况,无论是类中的测试还是 tearDownClass() 运行。在情况下 django.test.TestCase ,这将泄漏在中创建的事务 super() 这导致了各种症状,包括一些平台上的分段故障(在MacOS上报告)。如果你想故意提出一个例外,比如 unittest.SkipTest 在里面 setUpClass() ,在调用之前一定要做 super() 为了避免这种情况。

TransactionTestCase

class TransactionTestCase[源代码]

TransactionTestCase 继承自 SimpleTestCase 要添加一些特定于数据库的功能:

Django TestCase 类是更常用的子类 TransactionTestCase 它利用数据库事务工具来加快在每次测试开始时将数据库重置为已知状态的过程。然而,这样做的结果是,一些数据库行为不能在Django中进行测试 TestCase 类。例如,您不能测试一个代码块是否在事务中执行,这在使用时是必需的。 select_for_update() . 在这种情况下,您应该使用 TransactionTestCase .

TransactionTestCaseTestCase 除了将数据库重置为已知状态的方式以及测试代码测试提交和回滚效果的能力外,都是相同的:

  • A TransactionTestCase 通过截断所有表,在测试运行后重置数据库。一 TransactionTestCase 可以调用commit和rollback并观察这些调用对数据库的影响。

  • A TestCase 另一方面,在测试后不截断表。相反,它将测试代码包含在一个在测试结束时回滚的数据库事务中。这保证了测试结束时的回滚会将数据库恢复到初始状态。

警告

TestCase 在不支持回滚的数据库上运行(例如带有myisam存储引擎的mysql),以及 TransactionTestCase ,将通过从测试数据库中删除所有数据在测试结束时回滚。

应用程序 will not see their data reloaded ;如果您需要此功能(例如,第三方应用程序应启用此功能),可以设置 serialized_rollback = True 里面 TestCase 身体。

TestCase

class TestCase[源代码]

这是在Django中用于编写测试的最常见类。它继承自 TransactionTestCase (延期) SimpleTestCase )如果Django应用程序不使用数据库,请使用 SimpleTestCase .

类:

  • 将测试包装在两个嵌套的 atomic() 块:一个用于整个类,一个用于每个测试。因此,如果要测试某些特定的数据库事务行为,请使用 TransactionTestCase .

  • 在每个测试结束时检查可延迟的数据库约束。

它还提供了一种附加方法:

classmethod TestCase.setUpTestData()[源代码]

类级别 atomic 上面描述的块允许在类级别创建初始数据,对于整个 TestCase . 这种技术比使用 setUp() .

例如::

from django.test import TestCase


class MyTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        # Set up data for the whole TestCase
        cls.foo = Foo.objects.create(bar="Test")
        ...

    def test1(self):
        # Some test using self.foo
        ...

    def test2(self):
        # Some other test using self.foo
        ...

注意,如果测试在不支持事务的数据库上运行(例如,使用myisam引擎的mysql), setUpTestData() 在每次测试前都会被调用,否定了速度优势。

中指定给类属性的对象 setUpTestData() 必须支持使用创建深层拷贝 copy.deepcopy() 以便将它们与由每种测试方法执行的更改隔离。

classmethod TestCase.captureOnCommitCallbacks(using=DEFAULT_DB_ALIAS, execute=False)[源代码]

返回一个上下文管理器,它捕获 transaction.on_commit() 给定数据库连接的回调。它返回一个列表,其中包含上下文退出时捕获的回调函数。从这个列表中,您可以对回调进行断言,或者调用它们来调用它们的副作用,模拟提交。

using 要为其捕获回调的数据库连接的别名。

如果 executeTrue ,则当上下文管理器退出时,如果没有发生异常,将调用所有回调。这将在代码块包装后模拟提交。

例如::

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

class 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 FAQSelenium documentation 以获取更多信息。

测试用例功能

默认测试客户端

SimpleTestCase.client

中的每个测试用例 django.test.*TestCase 实例可以访问Django测试客户端的实例。此客户端可以访问为 self.client . 这个客户机是为每个测试重新创建的,因此您不必担心状态(如cookie)从一个测试传递到另一个测试。

这意味着,而不是实例化 Client 在每个测试中:

import unittest
from django.test import Client


class SimpleTest(unittest.TestCase):
    def test_details(self):
        client = Client()
        response = client.get("/customer/details/")
        self.assertEqual(response.status_code, 200)

    def test_index(self):
        client = Client()
        response = client.get("/customer/index/")
        self.assertEqual(response.status_code, 200)

……你可以参考 self.client ,像这样::

from django.test import TestCase


class SimpleTest(TestCase):
    def test_details(self):
        response = self.client.get("/customer/details/")
        self.assertEqual(response.status_code, 200)

    def test_index(self):
        response = self.client.get("/customer/index/")
        self.assertEqual(response.status_code, 200)

自定义测试客户端

SimpleTestCase.client_class

如果你想用不同的 Client 类(例如,具有自定义行为的子类),使用 client_class 类属性:

from django.test import Client, TestCase


class MyTestClient(Client):
    # Specialized methods for your environment
    ...


class MyTest(TestCase):
    client_class = MyTestClient

    def test_my_stuff(self):
        # Here self.client is an instance of MyTestClient...
        call_some_test_code()

夹具加载

TransactionTestCase.fixtures

如果数据库中没有任何数据,那么数据库支持的网站的测试用例类就没有多大用处。测试更具可读性,使用ORM创建对象也更易于维护,例如在 TestCase.setUpTestData() 但是,您也可以使用 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 ,设备将加载到所有指定的数据库中。

URLCONF配置

如果应用程序提供视图,您可能希望包括使用测试客户机来运行这些视图的测试。但是,最终用户可以自由地将视图部署到应用程序中他们选择的任何URL。这意味着您的测试不能依赖于这样一个事实:您的视图将在特定的URL上可用。用装饰你的测试类或测试方法 @override_settings(ROOT_URLCONF=...) 用于URLCONF配置。

多数据库支持

TransactionTestCase.databases

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()

此测试用例类将刷新 defaultother 在运行之前测试数据库 test_index_page_view 。您还可以使用 '__all__' 以指定必须刷新所有测试数据库。

这个 databases 标志还控制 TransactionTestCase.fixtures 加载到。默认情况下,设备仅加载到 default 数据库。

对不在中的数据库的查询 databases 将给出断言错误以防止测试之间的状态泄漏。

TestCase.databases

默认情况下,只有 default 数据库将在 TestCase 执行和尝试查询其他数据库将导致断言错误,以防止测试之间的状态泄漏。

使用 databases 测试类的class属性,用于请求对非``default``数据库进行事务包装。

例如::

class OtherDBTests(TestCase):
    databases = {"other"}

    def test_other_db_query(self):
        ...

此测试将只允许对 other 数据库。就像 SimpleTestCase.databasesTransactionTestCase.databases , the '__all__' 常量可用于指定测试应允许查询所有数据库。

替代设置

警告

使用下面的函数临时更改测试中设置的值。不要操纵 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/")

此示例将重写 LOGIN_URL 中的代码的设置 with 块,然后将其值重置为以前的状态。

SimpleTestCase.modify_settings()[源代码]

重新定义包含值列表的设置可能会很麻烦。实际上,添加或删除值通常就足够了。Django提供 modify_settings() 更容易更改设置的上下文管理器:

from django.test import TestCase


class MiddlewareTestCase(TestCase):
    def test_cache_middleware(self):
        with self.modify_settings(
            MIDDLEWARE={
                "append": "django.middleware.cache.FetchFromCacheMiddleware",
                "prepend": "django.middleware.cache.UpdateCacheMiddleware",
                "remove": [
                    "django.contrib.sessions.middleware.SessionMiddleware",
                    "django.contrib.auth.middleware.AuthenticationMiddleware",
                    "django.contrib.messages.middleware.MessageMiddleware",
                ],
            }
        ):
            response = self.client.get("/")
            # ...

对于每个操作,可以提供值列表或字符串。当值已存在于列表中时, appendprepend 没有效果;也没有效果 remove 当值不存在时。

override_settings(**kwargs)[源代码]

如果要重写测试方法的设置,Django将提供 override_settings() 装饰符(见) PEP 318 )使用方法如下:

from django.test import TestCase, override_settings


class LoginTestCase(TestCase):
    @override_settings(LOGIN_URL="/other/login/")
    def test_login(self):
        response = self.client.get("/sekrit/")
        self.assertRedirects(response, "/other/login/?next=/sekrit/")

也可以应用于 TestCase 类:

from django.test import TestCase, override_settings


@override_settings(LOGIN_URL="/other/login/")
class LoginTestCase(TestCase):
    def test_login(self):
        response = self.client.get("/sekrit/")
        self.assertRedirects(response, "/other/login/?next=/sekrit/")
modify_settings(*args, **kwargs)[源代码]

同样,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("/")
        # ...

备注

当给定一个类时,这些修饰符直接修改该类并返回它;它们不创建并返回该类的修改副本。因此,如果您尝试调整上述示例,以将返回值分配给与 LoginTestCaseMiddlewareTestCase ,您可能会惊讶地发现原来的测试用例类仍然同样受到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、存储

存储配置

Changed in Django Development version:

属性时重置默认渲染器 FORM_RENDERER 已添加设置已更改。

隔离应用程序

utils.isolate_apps(*app_labels, attr_name=None, kwarg_name=None)

将包装上下文中定义的模型注册到它们自己的独立模型中 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 参数。此字符串将作为断言生成的任何失败消息的前缀。这允许您提供其他详细信息,以帮助您确定测试套件中失败的位置和原因。

SimpleTestCase.assertRaisesMessage(expected_exception, expected_message, callable, *args, **kwargs)[源代码]
SimpleTestCase.assertRaisesMessage(expected_exception, expected_message)

断言执行 callable 加薪 expected_exceptionexpected_message 在异常的消息中找到。任何其他结果均报告为失败。这是一个简单的版本 unittest.TestCase.assertRaisesRegex() 不同的是 expected_message 不作为正则表达式处理。

如果只有 expected_exceptionexpected_message 给定参数后,返回上下文管理器,以便测试的代码可以内联写入,而不是作为函数写入:

with self.assertRaisesMessage(ValueError, "invalid literal for int()"):
    int("a")
SimpleTestCase.assertWarnsMessage(expected_warning, expected_message, callable, *args, **kwargs)[源代码]
SimpleTestCase.assertWarnsMessage(expected_warning, expected_message)

类似于 SimpleTestCase.assertRaisesMessage() 但为了 assertWarnsRegex() 而不是 assertRaisesRegex() .

SimpleTestCase.assertFieldOutput(fieldclass, valid, invalid, field_args=None, field_kwargs=None, empty_value='')[源代码]

断言窗体字段在各种输入下的行为正确。

参数:
  • fieldclass -- 要测试的字段的类别。

  • valid -- 将有效输入映射到其预期清除值的字典。

  • invalid -- 将无效输入映射到一个或多个引发的错误消息的字典。

  • field_args -- 传递的参数用于实例化字段。

  • field_kwargs -- 传递的Kwargs用于实例化字段。

  • empty_value -- 输入的预期清洁输出 empty_values .

例如,下面的代码测试 EmailField 接受 a@a.com 作为有效的电子邮件地址,但拒绝 aaa 带有合理的错误消息:

self.assertFieldOutput(
    EmailField, {"a@a.com": "a@a.com"}, {"aaa": ["Enter a valid email address."]}
)
SimpleTestCase.assertFormError(form, field, errors, msg_prefix='')[源代码]

断言窗体上的某个字段引发提供的错误列表。

form 是一种 Form 举个例子。表格必须是 bound 但不一定经过验证 (assertFormError() 将自动调用 full_clean() 在表格上)。

field 表单上要检查的域的名称。要检查表单的 non-field errors ,使用 field=None

errors 是该字段应包含的所有错误字符串的列表。如果您只希望出现一个错误,也可以传递单个错误字符串,这意味着 errors='error message' 与之相同 errors=['error message']

SimpleTestCase.assertFormSetError(formset, form_index, field, errors, msg_prefix='')[源代码]

断言 formset 在呈现时引发提供的错误列表。

formset 是一种 FormSet 举个例子。必须绑定表单集,但不一定要验证 (assertFormSetError() 将自动调用 full_clean() 在表格集上)。

form_index 中的表单编号。 FormSet (从0开始)。使用 form_index=None 检查表单集的非表单错误,即调用 formset.non_form_errors() 。在这种情况下,您还必须使用 field=None

fielderrors 与参数的含义相同 assertFormError()

SimpleTestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False)[源代码]

断言一个 response 产生了给定的 status_code 那就是 text 出现在其 content 。如果 count 是提供的, text 必须准确地发生 count 回应中的次数。

集合 htmlTrue 处理 text 作为HTML。与响应内容的比较将基于HTML语义,而不是逐字符相等。在大多数情况下,空白被忽略,属性排序不重要。见 assertHTMLEqual() 了解更多详细信息。

Changed in Django Development version:

在旧版本中,错误消息不包含响应内容。

SimpleTestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False)[源代码]

断言一个 response 产生了给定的 status_code 那就是 text 会吗? not 出现在其 content

集合 htmlTrue 处理 text 作为HTML。与响应内容的比较将基于HTML语义,而不是逐字符相等。在大多数情况下,空白被忽略,属性排序不重要。见 assertHTMLEqual() 了解更多详细信息。

Changed in Django Development version:

在旧版本中,错误消息不包含响应内容。

SimpleTestCase.assertTemplateUsed(response, template_name, msg_prefix='', count=None)[源代码]

断言在呈现响应时使用了具有给定名称的模板。

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")
SimpleTestCase.assertTemplateNotUsed(response, template_name, msg_prefix='')[源代码]

断言具有给定名称的模板是 not 用于呈现响应。

您可以使用它作为上下文管理器,方法与 assertTemplateUsed() .

SimpleTestCase.assertURLEqual(url1, url2, msg_prefix='')[源代码]

断言两个URL相同,忽略查询字符串参数的顺序(具有相同名称的参数除外)。例如, /path/?x=1&y=2 等于 /path/?y=2&x=1 ,但是 /path/?a=1&a=2 不等于 /path/?a=2&a=1 .

SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='', fetch_redirect_response=True)[源代码]

断言 response 返回了一个 status_code 重定向状态,重定向到 expected_url (包括任何 GET 数据),并且最终页面是与 target_status_code

如果您的请求使用 follow 论点 expected_urltarget_status_code 将是重定向链的最后一点的URL和状态代码。

如果 fetch_redirect_responseFalse ,将不加载最后一页。由于测试客户端无法获取外部URL,因此在 expected_url 不是你的django应用程序的一部分。

在比较两个URL时,正确处理方案。如果在我们重定向到的位置没有指定任何方案,则使用原始请求的方案。如果有,计划 expected_url 是用来比较的。

SimpleTestCase.assertHTMLEqual(html1, html2, msg=None)[源代码]

断言字符串 html1html2 一律平等。比较基于HTML语义。比较考虑了以下因素:

  • 忽略HTML标记前后的空白。

  • 所有类型的空白都被认为是等效的。

  • 所有打开的标记都是隐式关闭的,例如当周围的标记关闭或HTML文档结束时。

  • 空标签等同于它们的自动关闭版本。

  • HTML元素属性的顺序并不重要。

  • 布尔属性(如 checked )都等于名称和值相等的属性(请参见示例)。

  • 引用同一字符的文本、字符引用和实体引用是等效的。

以下示例是有效的测试,不会引发任何 AssertionError ::

self.assertHTMLEqual(
    "<p>Hello <b>&#x27;world&#x27;!</p>",
    """<p>
        Hello   <b>&#39;world&#39;! </b>
    </p>""",
)
self.assertHTMLEqual(
    '<input type="checkbox" checked="checked" id="id_accept_terms" />',
    '<input id="id_accept_terms" type="checkbox" checked>',
)

html1html2 必须包含HTML。一个 AssertionError 如果其中一个无法分析,则将引发。

出错时的输出可以用 msg 参数。

SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None)[源代码]

断言字符串 html1html2not 相等。比较基于HTML语义。见 assertHTMLEqual() 有关详细信息。

html1html2 必须包含HTML。一个 AssertionError 如果其中一个无法分析,则将引发。

出错时的输出可以用 msg 参数。

SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None)[源代码]

断言字符串 xml1xml2 是平等的。这种比较是基于XML语义的。类似于 assertHTMLEqual() ,是对解析的内容进行比较,因此只考虑语义差异,而不考虑语法差异。当在任何参数中传递无效的XML时, AssertionError 始终引发,即使两个字符串相同也是如此。

XML声明、文档类型、处理说明和注释将被忽略。只比较根元素及其子元素。

出错时的输出可以用 msg 参数。

SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None)[源代码]

断言字符串 xml1xml2not 相等。比较基于XML语义。见 assertXMLEqual() 有关详细信息。

出错时的输出可以用 msg 参数。

SimpleTestCase.assertInHTML(needle, haystack, count=None, msg_prefix='')[源代码]

断言该HTML片段 needle 包含在 haystack 一次。

如果 count 指定整数参数,然后再指定 needle 事件将被严格验证。

大多数情况下会忽略空格,并且属性排序并不重要。看见 assertHTMLEqual() 了解更多详细信息。

Changed in Django Development version:

在旧版本中,错误消息不包含 haystack

SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None)[源代码]

断言JSON片段 rawexpected_data 一律平等。当重量级被委托给 json 类库。

出错时的输出可以用 msg 参数。

SimpleTestCase.assertJSONNotEqual(raw, expected_data, msg=None)[源代码]

断言JSON片段 rawexpected_datanot 相等。见 assertJSONEqual() 更多详情。

出错时的输出可以用 msg 参数。

TransactionTestCase.assertQuerySetEqual(qs, values, transform=None, ordered=True, msg=None)[源代码]

断言查询集 qs 匹配值的特定可迭代值 values

如果 transform 被提供, values 与应用程序生成的列表进行比较。 transform 致每个成员 qs

默认情况下,比较也是顺序相关的。如果 qs 不提供隐式排序,则可以将 ordered 参数设置为 False ,这将比较变成一种 collections.Counter 比较一下。如果顺序未定义(如果给定的 qs 不是有序的,并且比较的对象是多个有序的值),则 ValueError 都被养大了。

出错时的输出可以用 msg 参数。

TransactionTestCase.assertNumQueries(num, func, *args, **kwargs)[源代码]

断言当 func 被调用 *args**kwargs 那个 num 执行数据库查询。

如果A "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 在任何测试中。

class AsyncClient(enforce_csrf_checks=False, raise_request_exception=True, *, headers=None, query_params=None, **defaults)[源代码]

AsyncClient 具有与同步(普通)测试客户端相同的方法和签名,但有以下例外:

  • 在初始化中,任意关键字参数 defaults 直接添加到ASGI作用域。

  • 标头作为 extra 关键字参数不应包含 HTTP_ 同步客户端所需的前缀(请参见 Client.get() )。例如,下面是如何设置一个HTTP Accept 标题:

    >>> c = AsyncClient()
    >>> c.get("/customers/details/", {"name": "fred", "age": 7}, ACCEPT="application/json")
    
Changed in Django 5.0:

支持 follow 参数已添加到 AsyncClient

Changed in Django Development version:

这个 query_params 添加了参数。

使用 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

在测试运行期间,每个发送的电子邮件都保存在 django.core.mail.outbox . 这是所有 EmailMessage 已发送的实例。这个 outbox 属性是创建的特殊属性 onlylocmem 使用电子邮件后端。它通常不作为 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", stdout=out)
        self.assertIn("Expected output", out.getvalue())

跳过测试

UnitTest库提供 @skipIf@skipUnless 如果提前知道这些测试在某些条件下会失败,那么修饰符允许您跳过测试。

例如,如果您的测试需要一个特定的可选库才能成功,您可以用 @skipIf . 然后,测试运行人员将报告没有执行测试以及为什么,而不是失败测试或完全忽略测试。

为了补充这些测试跳过行为,Django提供了两个额外的跳过修饰符。这些修饰符不是测试一般的布尔值,而是检查数据库的功能,如果数据库不支持特定的命名功能,则跳过测试。

装饰符使用字符串标识符来描述数据库功能。此字符串对应于数据库连接要素类的属性。看见 django.db.backends.base.features.BaseDatabaseFeatures class 获取可用作跳过测试的基础的数据库功能的完整列表。

skipIfDBFeature(*feature_name_strings)[源代码]

跳过装饰测试或 TestCase 如果支持所有命名数据库功能。

例如,如果数据库支持事务(例如, not 在PostgreSQL下运行,但在带有myisam表的mysql下运行)::

class MyTests(TestCase):
    @skipIfDBFeature("supports_transactions")
    def test_transaction_behavior(self):
        # ... conditional test code
        pass
skipUnlessDBFeature(*feature_name_strings)[源代码]

跳过装饰测试或 TestCase 如果任何命名数据库功能 not 支持。

例如,只有当数据库支持事务(例如,它将在PostgreSQL下运行,但是 not 在mysql和myisam表下)::

class MyTests(TestCase):
    @skipUnlessDBFeature("supports_transactions")
    def test_transaction_behavior(self):
        # ... conditional test code
        pass