Skip to main content

测试工具

Django提供了一组小工具,在写测试时派上用场。

测试客户端

测试客户端是一个Python类,作为一个虚拟的Web浏览器,允许您测试您的视图,并与您的Django供电的应用程序以编程方式交互。

你可以用测试客户端做的一些事情是:

  • 模拟对URL的GET和POST请求,并观察响应 - 从低级HTTP(结果头和状态代码)到页面内容的一切。

  • 查看重定向链(如果有),并在每个步骤中检查网址和状态代码。

  • 测试给定请求是否由给定的Django模板呈现,其中模板上下文包含某些值。

请注意,测试客户端不是要替代 Selenium 或其他“浏览器内”框架。 Django的测试客户端有不同的焦点。简而言之:

  • 使用Django的测试客户端来确定正在渲染正确的模板,并且传递正确的上下文数据。

  • 使用浏览器中的框架(如 Selenium)测试 rendered HTML和网页的 behavior,即JavaScript功能。 Django还为这些框架提供特殊支持;有关详细信息,请参阅 LiveServerTestCase 部分。

一个全面的测试套件应该使用两种测试类型的组合。

概述和一个快速示例

要使用测试客户端,请实例化 django.test.Client 并检索网页:

>>> from django.test import Client
>>> c = Client()
>>> response = c.post('/login/', {'username': 'john', 'password': 'smith'})
>>> response.status_code
200
>>> response = c.get('/customer/details/')
>>> response.content
b'<!DOCTYPE html...'

正如这个例子所建议的,您可以在Python交互式解释器的会话中实例化 Client

请注意测试客户端如何工作的几个重要的事情:

  • 测试客户端执行 not 要求Web服务器正在运行。事实上,它将运行很好,没有Web服务器运行在所有!这是因为它避免了HTTP的开销,直接处理Django框架。这有助于使单元测试快速运行。

  • 检索网页时,请记住指定网址的 path,而不是整个网域。例如,这是正确的:

    >>> c.get('/login/')
    

    这是不正确的:

    >>> c.get('https://www.example.com/login/')
    

    测试客户端无法检索不受您的Django项目驱动的网页。如果需要检索其他网页,请使用Python标准库模块(如 urllib)。

  • 要解析URL,测试客户端使用您的 ROOT_URLCONF 设置指向的任何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, **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)[源代码]

在提供的 path 上发出GET请求,并返回一个 Response 对象,这在下面记录。

data 字典中的键值对用于创建GET数据有效内容。例如:

>>> c = Client()
>>> c.get('/customers/details/', {'name': 'fred', 'age': 7})

...将导致对等于的GET请求的求值:

/customers/details/?name=fred&age=7

extra 关键字参数参数可用于指定要在请求中发送的头。例如:

>>> c = Client()
>>> c.get('/customers/details/', {'name': 'fred', 'age': 7},
...       HTTP_X_REQUESTED_WITH='XMLHttpRequest')

...将向详细信息视图发送HTTP头 HTTP_X_REQUESTED_WITH,这是一种测试使用 django.http.HttpRequest.is_ajax() 方法的代码路径的好方法。

CGI规范

通过 **extra 发送的报头应遵循 CGI 规范。例如,模拟在从浏览器到服务器的HTTP请求中发送的不同“主机”报头应该作为 HTTP_HOST 传递。

如果您已经有以URL编码形式的GET参数,则可以使用该编码,而不使用数据参数。例如,先前的GET请求也可以被建议为:

>>> c = Client()
>>> c.get('/customers/details/?name=fred&age=7')

如果您提供包含编码的GET数据和数据参数的网址,则数据参数将优先。

如果将 follow 设置为 True,则客户端将跟随任何重定向,并且将在包含中间URL和状态代码的元组的响应对象中设置 redirect_chain 属性。

如果您有一个网址 /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请求。

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

在提供的 path 上发出一个POST请求,并返回一个 Response 对象,这将在下面描述。

data 字典中的键值对用于提交POST数据。例如:

>>> c = Client()
>>> c.post('/login/', {'name': 'fred', 'passwd': 'secret'})

...将导致对此URL的POST请求的评估:

/login/

...使用此POST数据:

name=fred&passwd=secret

如果您提供 content_type (例如用于XML有效载荷的 text/xml),data 的内容将在POST请求中使用 content_type 在HTTP Content-Type 头中原样发送。

如果不提供 content_type 的值,则 data 中的值将以 multipart/form-data 的内容类型传输。在这种情况下,data 中的键值对将被编码为一个多部分消息,并用于创建POST数据有效负载。

要为给定的键提交多个值 - 例如,为 <select multiple> 指定选择 - 将值作为所需键的列表或元组提供。例如,data 的此值将为名为 choices 的字段提交三个选定的值:

{'choices': ('a', 'b', 'd')}

提交文件是一种特殊情况。要发布文件,您只需要提供文件字段名作为键,以及要作为值上传的文件的文件句柄。例如:

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

(这里的 attachment 名称不相关;使用您的文件处理代码所期望的任何名称。)

您还可以提供任何类似文件的对象(例如,StringIOBytesIO)作为文件句柄。

请注意,如果您希望对多个 post() 调用使用相同的文件句柄,则需要在帖子之间手动重置文件指针。最简单的方法是在文件提交给 post() 后手动关闭文件,如上所示。

您还应确保以允许读取数据的方式打开文件。如果您的文件包含二进制数据,如图像,这意味着您将需要以 rb (读取二进制)模式打开文件。

extra 参数的行为与 Client.get() 相同。

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

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

...处理此请求的视图可以询问request.POST以检索用户名和密码,并且可以询问request.GET以确定用户是否是访问者。

如果将 follow 设置为 True,则客户端将跟随任何重定向,并且将在包含中间URL和状态代码的元组的响应对象中设置 redirect_chain 属性。

如果将 secure 设置为 True,则客户端将模拟HTTPS请求。

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

在提供的 path 上发出HEAD请求并返回 Response 对象。此方法与 Client.get() 类似,包括 followsecureextra 参数,但它不返回消息体。

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

在提供的 path 上执行OPTIONS请求并返回 Response 对象。用于测试RESTful接口。

当提供 data 时,它被用作请求体,并且 Content-Type 报头被设置为 content_type

followsecureextra 参数的行为与 Client.get() 相同。

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

在提供的 path 上执行PUT请求并返回 Response 对象。用于测试RESTful接口。

当提供 data 时,它被用作请求体,并且 Content-Type 报头被设置为 content_type

followsecureextra 参数的行为与 Client.get() 相同。

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

在提供的 path 上发出PATCH请求并返回 Response 对象。用于测试RESTful接口。

followsecureextra 参数的行为与 Client.get() 相同。

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

在提供的 path 上执行DELETE请求并返回 Response 对象。用于测试RESTful接口。

当提供 data 时,它被用作请求体,并且 Content-Type 报头被设置为 content_type

followsecureextra 参数的行为与 Client.get() 相同。

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

在提供的 path 上进行TRACE请求并返回 Response 对象。用于模拟诊断探头。

不同于其他请求方法,data 是不是为了遵守 RFC 7231#section-4.3.8,该法案规定了TRACE请求不能有身体提供作为关键字参数。

followsecureextra 参数的行为与 Client.get() 相同。

login(**credentials)[源代码]

如果您的网站使用Django的 authentication system 并且处理用户登录,您可以使用测试客户端的 login() 方法来模拟用户登录网站的效果。

调用此方法后,测试客户端将拥有通过任何可能构成视图一部分的基于登录的测试所需的所有Cookie和会话数据。

credentials 参数的格式取决于您使用的 认证后端 (由 AUTHENTICATION_BACKENDS 设置配置)。如果您使用的是由Django(ModelBackend)提供的标准认证后端,则 credentials 应该是用户的用户名和密码,作为关键字参数提供:

>>> c = Client()
>>> c.login(username='fred', password='secret')

# Now you can access a view that's only available to logged-in users.

如果您使用的是其他身份验证后端,则此方法可能需要不同的凭据。它需要您的后端的 authenticate() 方法需要的任何凭据。

如果接受凭据并且登录成功,则 login() 返回 True

最后,您需要记住创建用户帐户,然后才能使用此方法。如上所述,测试运行器使用测试数据库执行,默认情况下不包含用户。因此,在生产站点上有效的用户帐户将无法在测试条件下工作。您需要创建用户作为测试套件的一部分 - 手动(使用Django模型API)或测试夹具。请记住,如果您希望测试用户具有密码,则不能通过直接设置密码属性来设置用户的密码 - 您必须使用 set_password() 函数来存储正确的散列密码。或者,您可以使用 create_user() 帮助程序方法创建具有正确散列密码的新用户。

Changed in Django 1.10:

在以前的版本中,不允许非活动用户(is_active=False)登录。

force_login(user, backend=None)
New in Django 1.9.

如果您的网站使用Django的 authentication system,您可以使用 force_login() 方法来模拟用户登录网站的效果。当测试需要用户登录并且用户登录的详细信息不重要时,请使用此方法而不是 login()

不像 login(),该方法跳过认证和验证步骤:非活动用户(is_active=False)被允许登录和用户的凭证不需要提供。

用户将其 backend 属性设置为 backend 参数的值(应为点分的Python路径字符串),如果未提供值,则将其设置为 settings.AUTHENTICATION_BACKENDS[0]login() 调用的 authenticate() 函数通常像这样注释用户。

此方法比 login() 快,因为绕过了昂贵的密码散列算法。此外,您可以通过 在测试时使用较弱的哈希 加速 login()

logout()[源代码]

如果您的网站使用Django的 authentication system,则 logout() 方法可用于模拟用户从您的网站注销的效果。

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

测试响应

get()post() 方法都返回一个 Response 对象。这个 Response 对象是 not,与Django视图返回的 HttpResponse 对象相同;测试响应对象具有一些对于测试代码进行验证有用的附加数据。

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

class Response
client

用于生成导致响应的请求的测试客户端。

content

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

context

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

如果呈现的页面使用多个模板,则 context 将是 Context 对象的列表,按照它们被呈现的顺序。

无论渲染期间使用的模板数量如何,都可以使用 [] 运算符检索上下文值。例如,可以使用来检索上下文变量 name:

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

不使用Django模板?

此属性仅在使用 DjangoTemplates 后端时填充。如果您使用另一个模板引擎,context_data 可能是具有该属性的响应的合适替代。

json(**kwargs)
New in Django 1.9.

响应的正文,解析为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 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'] 确定响应的内容类型。

例外

如果将测试客户端指向引发异常的视图,那么该异常将在测试用例中可见。然后,您可以使用标准 try ... except 块或 assertRaises() 来测试异常。

测试客户端不可见的唯一例外是 Http404PermissionDeniedSystemExitSuspiciousOperation。 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()

设置语言

当测试支持国际化和本地化的应用程序时,您可能需要设置测试客户端请求的语言。这样做的方法取决于 LocaleMiddleware 是否被启用。

如果启用中间件,则可以通过创建名称为 LANGUAGE_COOKIE_NAME 的cookie和语言代码的值来设置语言:

from django.conf import settings

def test_language_using_cookie(self):
    self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: 'fr'})
    response = self.client.get('/')
    self.assertEqual(response.content, b"Bienvenue sur mon site.")

或通过在请求中包括 Accept-Language HTTP头:

def test_language_using_header(self):
    response = self.client.get('/', HTTP_ACCEPT_LANGUAGE='fr')
    self.assertEqual(response.content, b"Bienvenue sur mon site.")

更多细节在 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提供了这个基类的一些扩展:

Hierarchy of Django unit testing classes (TestCase subclasses)

Django单元测试类的层次结构

将普通 unittest.TestCase 转换为任何子类很容易:将测试的基类从 unittest.TestCase 更改为子类。所有标准的Python单元测试功能都将可用,并且将增加一些有用的添加,如下面每节所述。

SimpleTestCase

class SimpleTestCase[源代码]

添加此功能的 unittest.TestCase 的子类:

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

SimpleTestCase.allow_database_queries
New in Django 1.9.

默认情况下,SimpleTestCase 不允许数据库查询。这有助于避免执行会影响其他测试的写入查询,因为每个 SimpleTestCase 测试不在事务中运行。如果您不关心这个问题,您可以通过将 allow_database_queries 类属性设置为测试类上的 True 来禁用此行为。

警告

SimpleTestCase 及其子类(例如 TestCase,...)依赖于 setUpClass()tearDownClass() 来执行一些类宽初始化(例如,覆盖设置)。如果你需要重写这些方法,不要忘了调用 super 实现:

class MyTestCase(TestCase):

    @classmethod
    def setUpClass(cls):
        super(MyTestCase, cls).setUpClass()
        ...

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

如果在 setUpClass() 期间引发异常,请务必考虑Python的行为。如果发生这种情况,类或 tearDownClass() 中的测试都不运行。在 django.test.TestCase 的情况下,这将泄露在 super() 中创建的事务,其导致各种症状,包括在一些平台上的分段故障(在OS X上报告)。如果你想在 setUpClass() 中故意引发一个异常,如 unittest.SkipTest,一定要在调用 super() 之前做到这一点,以避免这种情况。

TransactionTestCase

class TransactionTestCase[源代码]

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

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

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

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

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

警告

在不支持回滚的数据库上运行的 TestCase (例如,具有MyISAM存储引擎的MySQL)和所有 TransactionTestCase 实例将在测试结束时通过从测试数据库中删除所有数据来回滚。

应用程式 将不会看到他们的数据重新加载;如果您需要此功能(例如,第三方应用应启用此功能),您可以在 TestCase 主体中设置 serialized_rollback = True

TestCase

class TestCase[源代码]

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

班上:

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

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

Changed in Django 1.10:

添加了在每个测试结束时检查可延迟数据库约束。

它还提供了一种额外的方法:

classmethod TestCase.setUpTestData()[源代码]

上述类级 atomic 块允许在类级别创建初始数据,对于整个 TestCase 一次。与使用 setUp() 相比,该技术允许更快的测试。

例如:

from django.test import TestCase

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

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

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

请注意,如果测试在没有事务支持的数据库上运行(例如,MyISAM引擎的MySQL),则在每次测试之前将调用 setUpTestData(),否定速度优势。

注意不要修改测试方法中在 setUpTestData() 中创建的任何对象。在类级别完成的设置工作对内存对象的修改将在测试方法之间持续。如果您需要修改它们,您可以使用 refresh_from_db()setUp() 方法中重新加载它们。

LiveServerTestCase

class LiveServerTestCase[源代码]

LiveServerTestCase 基本上与 TransactionTestCase 一样,有一个额外的功能:它在设置的后台启动一个活动的Django服务器,并在拆卸时关闭它。这允许使用除了 Django虚拟客户端 之外的自动测试客户端(例如 Selenium 客户端)在浏览器内执行一系列功能测试并模拟真实用户的动作。

默认情况下,活动服务器侦听 localhost 并选择 8081-8179 范围内的第一个可用端口。在测试期间可以使用 self.live_server_url 访问其完整的URL。

Changed in Django 1.9:

在早期版本中,活动服务器的默认地址始终为 'localhost:8081'

如果您想要选择其他地址,则可以使用 test --liveserver 选项传递不同的地址,例如:

$ ./manage.py test --liveserver=localhost:8082
Changed in Django 1.9:

在旧版本中,只能从实例访问 live_server_url。它现在是一个类属性,可以从类方法如 setUpClass() 访问。

另一种更改默认服务器地址的方法是在代码中某处设置 DJANGO_LIVE_TEST_SERVER_ADDRESS 环境变量(例如,在 custom test runner 中):

import os
os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = 'localhost:8082'

在测试由多个进程并行运行(例如,在多个同时的 continuous integration 构建的上下文中)的情况下,进程将竞争同一地址,因此,您的测试可能随机失败,并且“地址已在使用“错误。为了避免此问题,您可以传递以逗号分隔的端口或端口范围列表(至少与可能的并行进程数一样多)。例如:

$ ./manage.py test --liveserver=localhost:8082,8090-8100,9000-9200,7041

然后,在测试执行期间,每个新的活测试服务器将尝试每个指定的端口,直到它找到一个可用的并且接受它。

为了演示如何使用 LiveServerTestCase,让我们编写一个简单的Selenium测试。首先,您需要将 selenium package 安装到Python路径中:

$ pip install selenium

然后,向应用程序的测试模块添加基于 LiveServerTestCase 的测试(例如:myapp/tests.py)。对于这个例子,我们假设你正在使用 staticfiles 应用程序,并希望在执行测试期间提供静态文件,类似于我们在开发时使用 DEBUG=True 获得的,即不必使用 collectstatic 收集它们。我们将使用提供该功能的 StaticLiveServerTestCase 子类。如果你不需要,用 django.test.LiveServerTestCase 替换它。

此测试的代码可能如下所示:

from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium.webdriver.firefox.webdriver import WebDriver

class MySeleniumTests(StaticLiveServerTestCase):
    fixtures = ['user-data.json']

    @classmethod
    def setUpClass(cls):
        super(MySeleniumTests, cls).setUpClass()
        cls.selenium = WebDriver()
        cls.selenium.implicitly_wait(10)

    @classmethod
    def tearDownClass(cls):
        cls.selenium.quit()
        super(MySeleniumTests, cls).tearDownClass()

    def test_login(self):
        self.selenium.get('%s%s' % (self.live_server_url, '/login/'))
        username_input = self.selenium.find_element_by_name("username")
        username_input.send_keys('myuser')
        password_input = self.selenium.find_element_by_name("password")
        password_input.send_keys('secret')
        self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()

最后,您可以运行测试如下:

$ ./manage.py test myapp.tests.MySeleniumTests.test_login

此示例将自动打开Firefox,然后转到登录页面,输入凭据并按“登录”按钮。 Selenium提供其他驱动程序,以防您没有安装Firefox或希望使用其他浏览器。上面的例子只是Selenium客户端可以做的一小部分;请查看 full reference 了解更多详情。

注解

当使用内存中的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 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 TestCase, Client

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

class MyTest(TestCase):
    client_class = MyTestClient

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

夹具装载

TransactionTestCase.fixtures

如果数据库中没有任何数据,那么对于数据库支持的网站的测试用例没有多大用处。测试更具可读性,使用ORM创建对象更加可维护,例如在 TestCase.setUpTestData() 中,然而,您也可以使用fixture。

fixture是Django知道如何导入到数据库中的数据集合。例如,如果您的网站有用户帐户,您可以设置假的用户帐户夹具,以便在测试期间填充您的数据库。

创建灯具的最直接的方法是使用 manage.py dumpdata 命令。这假设您已经在数据库中有一些数据。有关详细信息,请参阅 dumpdata documentation

一旦你创建了一个fixture并把它放在你的一个 INSTALLED_APPSfixtures 目录中,你可以在你的单元测试中使用它,在 django.test.TestCase 子类中指定一个 fixtures 类属性:

from django.test import TestCase
from myapp.models import Animal

class AnimalTestCase(TestCase):
    fixtures = ['mammals.json', 'birds']

    def setUp(self):
        # Test definitions as before.
        call_setup_methods()

    def testFluffyAnimals(self):
        # A test that uses the fixtures.
        call_some_test_code()

下面具体说明会发生什么:

  • 在每个测试开始时,在运行 setUp() 之前,Django将刷新数据库,将数据库返回到在调用 migrate 后直接处于的状态。

  • 然后,安装所有命名的夹具。在这个例子中,Django将安装任何名为 mammals 的JSON夹具,其次是任何名为 birds 的夹具。有关定义和安装灯具的更多详细信息,请参阅 loaddata 文档。

出于性能原因,TestCasesetUpTestData() 之前为整个测试类加载器件,而不是在每个测试之前加载器件,并且在每次测试之前使用事务来清理数据库。在任何情况下,您都可以确定测试的结果不会受到另一个测试或测试执行顺序的影响。

默认情况下,fixture仅加载到 default 数据库中。如果您使用多个数据库并设置 multi_db=True,fixture将被加载到所有数据库。

URLconf配置

如果应用程序提供了视图,则可能需要包括使用测试客户端来执行这些视图的测试。但是,最终用户可以在自己选择的任何URL上自由地在应用程序中部署视图。这意味着您的测试不能依赖于您的视图将在特定网址可用的事实。使用 @override_settings(ROOT_URLCONF=...) 为URLconf配置装饰测试类或测试方法。

多数据库支持

TransactionTestCase.multi_db

Django设置一个测试数据库,对应于在设置文件中的 DATABASES 定义中定义的每个数据库。但是,运行Django TestCase所需的大部分时间由调用 flush 来消耗,以确保在每次测试运行开始时都有一个干净的数据库。如果您有多个数据库,需要多次刷新(每个数据库一个),这可能是一个耗时的活动 - 特别是如果您的测试不需要测试多数据库活动。

作为优化,Django只在每次测试运行开始时刷新 default 数据库。如果您的设置包含多个数据库,并且测试需要每个数据库都是干净的,则可以使用测试套件上的 multi_db 属性请求完全刷新。

例如:

class TestMyViews(TestCase):
    multi_db = True

    def test_index_page_view(self):
        call_some_test_code()

此测试用例将在运行 test_index_page_view 之前刷新 all 测试数据库。

multi_db 标志还影响attr:TransactionTestCase.fixtures 加载到哪些数据库中。默认情况下(当 multi_db=False 时),fixtures只加载到 default 数据库。如果 multi_db=True,fixtures被加载到所有数据库。

覆盖设置

警告

使用以下功能临时更改测试中的设置值。不要直接操作 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()[源代码]

它可以证明难以重新定义包含值列表的设置。实际上,添加或删除值通常就足够了。 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()[源代码]

如果你想覆盖一个测试方法的设置,Django提供了 override_settings() 装饰器(见 PEP 318)。它像这样使用:

from django.test import TestCase, override_settings

class LoginTestCase(TestCase):

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

装饰器也可以应用于 TestCase 类:

from django.test import TestCase, override_settings

@override_settings(LOGIN_URL='/other/login/')
class LoginTestCase(TestCase):

    def test_login(self):
        response = self.client.get('/sekrit/')
        self.assertRedirects(response, '/other/login/?next=/sekrit/')
modify_settings()[源代码]

同样,Django提供了 modify_settings() 装饰器:

from django.test import TestCase, modify_settings

class MiddlewareTestCase(TestCase):

    @modify_settings(MIDDLEWARE={
        'append': 'django.middleware.cache.FetchFromCacheMiddleware',
        'prepend': 'django.middleware.cache.UpdateCacheMiddleware',
    })
    def test_cache_middleware(self):
        response = self.client.get('/')
        # ...

装饰器也可以应用于测试用例类:

from django.test import TestCase, modify_settings

@modify_settings(MIDDLEWARE={
    'append': 'django.middleware.cache.FetchFromCacheMiddleware',
    'prepend': 'django.middleware.cache.UpdateCacheMiddleware',
})
class MiddlewareTestCase(TestCase):

    def test_cache_middleware(self):
        response = self.client.get('/')
        # ...

注解

当给定一个类时,这些装饰器直接修改类并返回它;他们不创建并返回它的修改副本。因此,如果您尝试调整上述示例以将返回值分配给与 LoginTestCaseMiddlewareTestCase 不同的名称,您可能会惊讶地发现,原始的测试用例类仍然受到装饰器的同等影响。对于给定的类,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本身使用这个信号来重置各种数据:

覆盖的设置

数据复位

USE_TZ,TIME_ZONE

数据库时区

模板

模板引擎

SERIALIZATION_MODULES

序列化器缓存

LOCALE_PATHS,LANGUAGE_CODE

默认翻译和加载翻译

MEDIA_ROOT,DEFAULT_FILE_STORAGE

默认文件存储

清空测试发件箱

如果使用任何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_exception,并且在异常的消息中发现 expected_message。任何其他结果报告为失败。这是一个更简单的 unittest.TestCase.assertRaisesRegex() 版本,区别在于 expected_message 不被当作正则表达式。

如果仅给出 expected_exceptionexpected_message 参数,则返回上下文管理器,以便被测试的代码可以被内联而不是作为函数:

with self.assertRaisesMessage(ValueError, 'invalid literal for int()'):
    int('a')

1.9 版后已移除: 不推荐将 callable 作为称为 callable_obj 的关键字参数。将可调用作为位置参数。

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

断言表单字段在各种输入中正确运行。

参数:
  • fieldclass – 要测试的字段的类。
  • valid – 一个将有效输入映射到其预期清除值的字典。
  • invalid – 将无效输入映射到一个或多个引起的错误消息的字典。
  • field_args – args传递来实例化字段。
  • field_kwargs – kwargs传递来实例化字段。
  • empty_valueempty_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_indexFormset 中表单的编号。如果 form_index 的值为 None,则将检查非格式错误(可通过 formset.non_form_errors() 访问的错误)。

field 是要检查的表单上的字段的名称。如果 field 的值为 None,则将检查非字段错误(可通过 form.non_field_errors() 访问的错误)。

errors 是一个错误字符串,或一个错误字符串列表,预期作为表单验证的结果。

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

断言 Response 实例产生给定的 status_code,并且 text 出现在响应的内容中。如果提供 counttext 必须在响应中恰好出现 count 次。

html 设置为 True 以将 text 作为HTML处理。与响应内容的比较将基于HTML语义,而不是逐个字符的等同。在大多数情况下忽略空白,属性排序不重要。详情请参阅 assertHTMLEqual()

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

认为 Response 实例产生给定的 status_code,并且 text 确实 not 出现在响应的内容中。

html 设置为 True 以将 text 作为HTML处理。与响应内容的比较将基于HTML语义,而不是逐个字符的等同。在大多数情况下忽略空白,属性排序不重要。详情请参阅 assertHTMLEqual()

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

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

名称是一个字符串,如 'admin/index.html'

count参数是一个整数,表示模板应该渲染的次数。默认是 None,意味着模板应该呈现一次或多次。

你可以使用它作为上下文管理器,像这样:

with self.assertTemplateUsed('index.html'):
    render_to_string('index.html')
with self.assertTemplateUsed(template_name='index.html'):
    render_to_string('index.html')
SimpleTestCase.assertTemplateNotUsed(response, template_name, msg_prefix='')[源代码]

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

您可以使用它作为上下文管理器与 assertTemplateUsed() 相同的方式。

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

认为响应返回了 status_code 重定向状态,重定向到 expected_url (包括任何 GET 数据),并且最后一页是与 target_status_code 一起接收的。

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

如果 fetch_redirect_responseFalse,则不会加载最后一页。由于测试客户端无法获取外部网址,因此如果 expected_url 不是您的Django应用程序的一部分,则这是特别有用的。

在两个URL之间进行比较时,会正确处理Scheme。如果在我们重定向到的位置中没有指定任何方案,则使用原始请求的方案。如果存在,expected_url 中的方案是用于进行比较的方案。

1.9 版后已移除: host 参数已弃用,因为重定向不再强制为绝对URL。

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

断言字符串 html1html2 是相等的。比较基于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>'
)

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,即使两个字符串都相同。

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

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

认为字符串 xml1xml2not 相等。比较基于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片段 rawexpected_data 是相等的。通常的JSON非重要空格规则适用于将重量级委派给 json 库。

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

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

断言JSON片段 rawexpected_datanot 相等的。详情请参阅 assertJSONEqual()

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

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

断言查询集 qs 返回值 values 的特定列表。

使用函数 transform 进行 qsvalues 的含量的比较;默认情况下,这意味着每个值的 repr() 进行比较。如果 repr() 不提供唯一或有帮助的比较,可以使用任何其他可呼叫。

默认情况下,比较也依赖于顺序。如果 qs 不提供隐式排序,您可以将 ordered 参数设置为 False,这将比较转换为 collections.Counter 比较。如果顺序未定义(如果给定的 qs 不是有序的并且比较是针对多于一个有序值),则引发 ValueError

出错时的输出可以用 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")

标记测试

New in Django 1.10.

您可以标记测试,以便可以轻松运行特定子集。例如,您可以标记快速或慢速测试:

from django.test import tag

class SampleTestCase(TestCase):

    @tag('fast')
    def test_fast(self):
        ...

    @tag('slow')
    def test_slow(self):
        ...

    @tag('slow', 'core')
    def test_slow_but_core(self):
        ...

您还可以标记测试用例:

@tag('slow', 'core')
class SampleTestCase(TestCase):
    ...

然后你可以选择运行哪些测试。例如,仅运行快速测试:

$ ./manage.py test --tag=fast

或者运行快速测试和核心一个(即使它很慢):

$ ./manage.py test --tag=fast --tag=core

您也可以按标签排除测试。要运行核心测试,如果它们不慢:

$ ./manage.py test --tag=core --exclude-tag=slow

test --exclude-tag 优先于 test --tag,因此如果测试有两个标签,并选择其中一个并排除另一个,则不会运行测试。

电子邮件服务

如果您的任何Django视图使用 Django的电子邮件功能 发送电子邮件,您可能不想每次使用该视图运行测试时发送电子邮件。因此,Django的测试运行器会自动将所有Django发送的电子邮件重定向到一个虚拟发件箱。这使您可以测试发送电子邮件的每个方面 - 从发送到每封邮件内容的邮件数量,而不实际发送邮件。

测试运行器通过用测试后端透明地替换普通电子邮件后端来实现这一点。 (不要担心,这对Django之外的任何其他电子邮件发件人(例如您的计算机的邮件服务器,如果您正在运行)没有任何影响。)

django.core.mail.outbox

在测试运行期间,每封发送的电子邮件都保存在 django.core.mail.outbox 中。这是一个已发送的所有 EmailMessage 实例的简单列表。 outbox 属性是使用 locmem 电子邮件后端时创建的 only 的特殊属性。它通常不作为 django.core.mail 模块的一部分存在,您不能直接导入它。下面的代码显示了如何正确访问此属性。

这里是一个示例测试,检查 django.core.mail.outbox 的长度和内容:

from django.core import mail
from django.test import TestCase

class EmailTest(TestCase):
    def test_send_email(self):
        # Send message.
        mail.send_mail(
            'Subject here', 'Here is the message.',
            'from@example.com', ['to@example.com'],
            fail_silently=False,
        )

        # Test that one message has been sent.
        self.assertEqual(len(mail.outbox), 1)

        # Verify that the subject of the first message is correct.
        self.assertEqual(mail.outbox[0].subject, 'Subject here')

先前 所述,测试发件箱在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

例如,如果数据库支持事务(例如,它将 not 在PostgreSQL下运行,但是在MySQL和MyISAM表下运行),则不会执行以下测试。:

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

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

例如,以下测试将仅在数据库支持事务时执行(例如,它将在PostgreSQL下运行,但是在带有MyISAM表的MySQL下运行 not):

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