高级测试主题

请求工厂

RequestFactory[source]

The RequestFactory shares the same API as the test client. 但是,RequestFactory并不像浏览器那样工作,它提供了一种生成请求实例的方法,可以将其作为任何视图的第一个参数。 这意味着您可以像测试任何其他函数一样来测试视图函数 - 就像一个黑箱一样,具有完全已知的输入,测试特定的输出。

RequestFactory的API是测试客户端API的一个稍有限制的子集:

  • It only has access to the HTTP methods get(), post(), put(), delete(), head(), options(), and trace().
  • 这些方法接受所有相同的参数,除follow之外的 由于这只是一个生产请求的工厂,所以要由您来处理。
  • 它不支持中间件。 会话和认证属性必须由测试本身提供,如果视图正常运行所需的话。

实施例¶ T0>

以下是使用请求工厂的简单单元测试:

from django.contrib.auth.models import AnonymousUser, User
from django.test import TestCase, RequestFactory

from .views import MyView, my_view

class SimpleTest(TestCase):
    def setUp(self):
        # Every test needs access to the request factory.
        self.factory = RequestFactory()
        self.user = User.objects.create_user(
            username='jacob', email='jacob@…', password='top_secret')

    def test_details(self):
        # Create an instance of a GET request.
        request = self.factory.get('/customer/details')

        # Recall that middleware are not supported. You can simulate a
        # logged-in user by setting request.user manually.
        request.user = self.user

        # Or you can simulate an anonymous user by setting request.user to
        # an AnonymousUser instance.
        request.user = AnonymousUser()

        # Test my_view() as if it were deployed at /customer/details
        response = my_view(request)
        # Use this syntax for class-based views.
        response = MyView.as_view()(request)
        self.assertEqual(response.status_code, 200)

测试和多个主机名

运行测试时验证ALLOWED_HOSTS设置。 这允许测试客户端区分内部和外部URL。

Projects that support multitenancy or otherwise alter business logic based on the request’s host and use custom host names in tests must include those hosts in ALLOWED_HOSTS.

第一个也是最简单的选择是将主机添加到您的设置文件中。 例如,docs.djangoproject.com的测试套件包含以下内容:

from django.test import TestCase

class SearchFormTestCase(TestCase):
    def test_empty_get(self):
        response = self.client.get('/en/dev/search/', HTTP_HOST='docs.djangoproject.dev:8000')
        self.assertEqual(response.status_code, 200)

并且设置文件包含项目支持的域列表:

ALLOWED_HOSTS = [
    'www.djangoproject.dev',
    'docs.djangoproject.dev',
    ...
]

另一个选择是使用override_settings()modify_settings()将所需主机添加到ALLOWED_HOSTS 此选项可能更适用于无法打包自己的设置文件的独立应用程序或者域列表不是静态的项目(例如,多租户的子域)。 例如,您可以按如下方式为域http://otherserver/编写一个测试:

from django.test import TestCase, override_settings

class MultiDomainTestCase(TestCase):
    @override_settings(ALLOWED_HOSTS=['otherserver'])
    def test_other_domain(self):
        response = self.client.get('http://otherserver/foo/bar/')

)运行测试时禁用ALLOWED_HOSTS检查(ALLOWED_HOSTS = ['*']防止测试客户端在重定向到外部URL时发出有用的错误消息。

在Django 1.11中更改:

旧版本在测试时没有验证ALLOWED_HOSTS,所以这些技术是不必要的。

测试和多个数据库

测试主/副本配置

如果您正在测试具有主/副本(称为主/从数据库)复制的多数据库配置,则创建测试数据库的这种策略会产生问题。 创建测试数据库时,将不会有任何复制,因此在主数据库上创建的数据将不会在副本上显示。

为了弥补这一点,Django允许你定义一个数据库是一个测试镜像 考虑以下(简化)示例数据库配置:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'myproject',
        'HOST': 'dbprimary',
         # ... plus some other settings
    },
    'replica': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'myproject',
        'HOST': 'dbreplica',
        'TEST': {
            'MIRROR': 'default',
        },
        # ... plus some other settings
    }
}

In this setup, we have two database servers: dbprimary, described by the database alias default, and dbreplica described by the alias replica. As you might expect, dbreplica has been configured by the database administrator as a read replica of dbprimary, so in normal activity, any write to default will appear on replica.

如果Django创建了两个独立的测试数据库,这将打破任何期望复制发生的测试。 然而,replica数据库已被配置为测试镜像(使用MIRROR测试设置),表明在测试中应该处理replica作为default的镜像。

在配置测试环境时,replica的测试版本将不会创建 相反,连接到replica将被重定向到指向default 因此,写入default将出现在replica上 - 但是因为它们实际上是相同的数据库,而不是因为两个数据库之间存在数据复制。

控制测试数据库的创建顺序

默认情况下,Django将假定所有的数据库依赖于default数据库,因此总是首先创建default数据库。 但是,在您的测试设置中,不保证其他数据库的创建顺序。

如果您的数据库配置需要特定的创建顺序,则可以使用DEPENDENCIES测试设置指定存在的依赖关系。 考虑以下(简化)示例数据库配置:

DATABASES = {
    'default': {
        # ... db settings
        'TEST': {
            'DEPENDENCIES': ['diamonds'],
        },
    },
    'diamonds': {
        # ... db settings
        'TEST': {
            'DEPENDENCIES': [],
        },
    },
    'clubs': {
        # ... db settings
        'TEST': {
            'DEPENDENCIES': ['diamonds'],
        },
    },
    'spades': {
        # ... db settings
        'TEST': {
            'DEPENDENCIES': ['diamonds', 'hearts'],
        },
    },
    'hearts': {
        # ... db settings
        'TEST': {
            'DEPENDENCIES': ['diamonds', 'clubs'],
        },
    }
}

在这个配置下,首先创建diamonds数据库,因为它是唯一没有依赖关系的数据库别名。 The default and clubs alias will be created next (although the order of creation of this pair is not guaranteed), then hearts, and finally spades.

如果DEPENDENCIES定义中存在任何循环依赖关系,则会引发ImproperlyConfigured异常。

TransactionTestCase 的高级功能

TransactionTestCase。 available_apps T0> ¶ T1>

警告

这个属性是一个私有的API。 在将来可能会被更改或删除,而不会有贬低的时间,例如,以适应应用程序加载的变化。

它被用来优化Django自己的测试套件,它包含数百个模型,但不同应用程序中的模型之间没有关系。

默认情况下,available_apps设置为None 在每次测试之后,Django调用flush重置数据库状态。 这将清空所有表并发出post_migrate信号,该信号为每个模型重新创建一个内容类型和三个权限。 这个操作与模型的数量成比例。

available_apps设置为应用程序列表指示Django的行为就好像只有来自这些应用程序的模型可用。 The behavior of TransactionTestCase changes as follows:

  • post_migrate is fired before each test to create the content types and permissions for each model in available apps, in case they’re missing.
  • 在每次测试之后,Django只清空对应于可用应用程序中模型的表格。 但是,在数据库级别,截断可能会级联到不可用应用中的相关模型。 此外,post_migrate未被触发;在选择了正确的一组应用程序之后,它将被下一个TransactionTestCase触发。

由于数据库没有完全刷新,如果一个测试创建了available_apps中未包含的模型的实例,它们将会泄漏,并且可能导致不相关的测试失败。 小心使用会话的测试;默认会话引擎将它们存储在数据库中。

由于post_migrate在刷新数据库后不会发出,所以在TransactionTestCase之后的状态与TestCase之后的状态不同:它缺少由听众创建的post_migrate行。 考虑到order in which tests are executed,这不是问题,只要给定的测试套件中的所有TransactionTestCase声明available_apps,或没有一个。

在Django自己的测试套件中,available_apps是强制性的。

TransactionTestCase。 reset_sequences T0> ¶ T1>

Setting reset_sequences = True on a TransactionTestCase will make sure sequences are always reset before the test run:

class TestsThatDependsOnPrimaryKeySequences(TransactionTestCase):
    reset_sequences = True

    def test_animal_pk(self):
        lion = Animal.objects.create(name="lion", sound="roar")
        # lion.pk is guaranteed to always be 1
        self.assertEqual(lion.pk, 1)

除非明确测试主键序列号,否则建议您不要在测试中对主键值进行硬编码。

使用reset_sequences = True会降低测试速度,因为主键重置是相对昂贵的数据库操作。

使用Django测试运行器来测试可重用应用程序

如果您正在编写reusable application,则可能需要使用Django测试运行器来运行您自己的测试套件,从而受益于Django测试基础架构。

通常的做法是在应用程序代码旁边的tests目录,结构如下:

runtests.py
polls/
    __init__.py
    models.py
    ...
tests/
    __init__.py
    models.py
    test_settings.py
    tests.py

让我们来看看这些文件中的几个:

runtests.py
#!/usr/bin/env python
import os
import sys

import django
from django.conf import settings
from django.test.utils import get_runner

if __name__ == "__main__":
    os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.test_settings'
    django.setup()
    TestRunner = get_runner(settings)
    test_runner = TestRunner()
    failures = test_runner.run_tests(["tests"])
    sys.exit(bool(failures))

这是您调用来运行测试套件的脚本。 它设置Django环境,创建测试数据库并运行测试。

为了清楚起见,这个例子只包含了使用Django测试运行器的最低限度的必要条件。 您可能需要添加命令行选项来控制详细程度,传递特定的测试标签来运行等。

测试/ test_settings.py
SECRET_KEY = 'fake-key'
INSTALLED_APPS = [
    "tests",
]

该文件包含运行应用程序测试所需的Django settings

再次,这是一个最小的例子;您的测试可能需要额外的设置才能运行。

由于tests包在运行测试时包含在INSTALLED_APPS中,因此您可以在models.py文件中定义仅测试模型。

使用不同的测试框架

Clearly, unittest is not the only Python testing framework. 虽然Django没有提供对替代框架的明确支持,但它确实提供了一种方法来调用构建替代框架的测试,就好像它们是正常的Django测试一样。

运行./ manage.py test时,Django会查看TEST_RUNNER设置来确定要执行的操作。 默认情况下,TEST_RUNNER指向'django.test.runner.DiscoverRunner' 这个类定义了默认的Django测试行为。 这种行为涉及:

  1. 执行全局预测试设置。
  2. 在名称与模式test*.py匹配的当前目录下的任何文件中查找测试。
  3. 创建测试数据库。
  4. 运行migrate以将模型和初始数据安装到测试数据库中。
  5. 运行system checks
  6. 运行找到的测试。
  7. 销毁测试数据库。
  8. 执行全球后测试拆解。
在Django 1.11中更改:

运行系统检查已添加。

如果你定义了你自己的测试运行器类,并指向TEST_RUNNER,那么当你运行./ manage.py 测试时,Django将执行你的测试运行器 T5> T3>。 通过这种方式,可以使用任何可以从Python代码执行的测试框架,或者修改Django测试执行过程来满足您可能具有的任何测试需求。

定义一个测试运行器

测试运行器是定义run_tests()方法的类。 Django提供了一个DiscoverRunner类,该类定义了默认的Django测试行为。 这个类定义了run_tests()入口点以及run_tests()用来设置,执行和拆除测试套件的其他方法。

DiscoverRunner(pattern='test*.py', top_level=None, verbosity=1, interactive=True, failfast=False, keepdb=False, reverse=False, debug_mode=False, debug_sql=False, **kwargs)[source]

DiscoverRunner will search for tests in any file matching pattern.

top_level can be used to specify the directory containing your top-level Python modules. 通常Django可以自动计算出来,所以没有必要指定这个选项。 如果指定,通常应该是包含manage.py文件的目录。

verbosity determines the amount of notification and debug information that will be printed to the console; 0 is no output, 1 is normal output, and 2 is verbose output.

如果interactiveTrue,测试套件有权在执行测试套件时询问用户的指令。 这种行为的一个例子是要求删除现有的测试数据库的权限。 如果interactiveFalse,则测试套件必须能够在没有任何手动干预的情况下运行。

如果failfastTrue,则在检测到第一个测试失败后,测试套件将停止运行。

如果keepdbTrue,测试套件将使用现有的数据库,或者根据需要创建一个。 如果False,将创建​​一个新的数据库,提示用户删除现有的数据库。

如果reverseTrue,测试用例将以相反的顺序执行。 这对调试没有正确隔离并有副作用的测试非常有用。 使用此选项时,Grouping by test class将被保留。

debug_mode指定在运行测试之前应将DEBUG设置设置为什么。

如果debug_sqlTrue,失败的测试用例会输出记录到django.db.backends logger的SQL查询以及追踪。 如果verbosity2,则输出所有测试中的查询。

Django可能会不时地通过添加新的参数来扩展测试运行者的功能。 **kwargs声明允许这种扩展。 如果您继承DiscoverRunner或编写您自己的测试运行器,请确保它接受**kwargs

您的测试运行者也可以定义其他的命令行选项。 Create or override an add_arguments(cls, parser) class method and add custom arguments by calling parser.add_argument() inside the method, so that the test command will be able to use those arguments.

Django 1.11新增功能:

添加了debug_mode关键字参数。

属性¶ T0>

DiscoverRunner。 test_suite T0> ¶ T1>

该类用于构建测试套件。 默认情况下,它被设置为unittest.TestSuite 如果你想实现不同的逻辑来收集测试,这可以被覆盖。

DiscoverRunner。 test_runner T0> ¶ T1>

这是用于执行单个测试并格式化结果的低级测试运行器的类。 默认情况下,它被设置为unittest.TextTestRunner 尽管命名约定有着不同的相似之处,但它与DiscoverRunner类型不同,它涵盖了更广泛的职责。 您可以覆盖此属性以修改测试运行和报告的方式。

DiscoverRunner。 test_loader T0> ¶ T1>

这是加载测试的类,无论是从TestCases还是模块或其他,并将它们捆绑到测试套件中供跑步者执行。 默认情况下,它被设置为unittest.defaultTestLoader 如果您的测试将以不同寻常的方式加载,您可以重写此属性。

方法¶ T0>

DiscoverRunner。run_teststest_labelsextra_tests = None** kwargs[source] ¶ T6>

运行测试套件。

test_labels allows you to specify which tests to run and supports several formats (see DiscoverRunner.build_suite() for a list of supported formats).

extra_tests is a list of extra TestCase instances to add to the suite that is executed by the test runner. 这些额外的测试是在test_labels中列出的模块中发现的额外测试运行的。

这个方法应该返回失败的测试次数。

类方法 DiscoverRunner。add_arguments(parser)[source]

重写此类方法以添加由test管理命令接受的自定义参数。 有关将参数添加到解析器的详细信息,请参见argparse.ArgumentParser.add_argument()

DiscoverRunner。setup_test_environment(**kwargs)[source]

通过调用setup_test_environment()并将DEBUG设置为self.debug_mode(默认为False)来设置测试环境。 。

DiscoverRunner。build_suitetest_labelsextra_tests = None** kwargs[source] ¶ T6>

构建一个与提供的测试标签相匹配的测试套件。

test_labels is a list of strings describing the tests to be run. 测试标签可以采取以下四种形式之一:

  • path.to.test_module.TestCase.test_method - 在测试用例中运行单个测试方法。
  • path.to.test_module.TestCase - 运行测试用例中的所有测试方法。
  • path.to.module - 在指定的Python包或模块中搜索并运行所有测试。
  • path/to/directory - 搜索并运行指定目录下的所有测试。

如果test_labels的值为None,则测试运行器将搜索当前目录下名称与其pattern匹配的所有文件中的测试(请参阅以上)。

extra_tests is a list of extra TestCase instances to add to the suite that is executed by the test runner. 这些额外的测试是在test_labels中列出的模块中发现的额外测试运行的。

返回准备运行的TestSuite实例。

DiscoverRunner。setup_databases(**kwargs)[source]

通过调用setup_databases()创建测试数据库。

DiscoverRunner。run_checks()[source]
Django 1.11新增功能

运行system checks

DiscoverRunner。run_suite tt>(suite** kwargs[source]

运行测试套件。

返回运行测试套件产生的结果。

DiscoverRunner。get_test_runner_kwargs()[source]
Django 1.11新增功能

返回关键字参数,以便用DiscoverRunner.test_runner实例化。

DiscoverRunner。teardown_databasesold_config** kwargs[source]

销毁测试数据库,通过调用teardown_databases()来恢复预测试条件。

DiscoverRunner。teardown_test_environment(**kwargs)[source]

恢复预测试环境。

DiscoverRunner。suite_resultsuiteresult** kwargs[source] ¶ T6>

计算并返回基于测试套件的返回代码以及该测试套件的结果。

测试实用程序

django.test.utils

为了帮助创建自己的测试运行器,Django在django.test.utils模块中提供了许多实用方法。

setup_test_environment(debug=None)[source]

执行全局预测试设置,例如为模板渲染系统安装检测工具并设置虚拟电子邮件发件箱。

如果debug不是None,则DEBUG设置更新为其值。

在Django 1.11中更改:

添加了debug参数。

teardown_test_environment()[source]

执行全局测试后拆卸,例如从模板系统中删除测试工具并恢复正常的电子邮件服务。

setup_databases(verbosity, interactive, keepdb=False, debug_sql=False, parallel=0, **kwargs)[source]
Django 1.11新增功能

创建测试数据库。

返回一个数据结构,该数据结构提供了足够的详细信息来撤消所做的更改。 这些数据将在测试结束时提供给teardown_databases()函数。

teardown_databases(old_config, parallel=0, keepdb=False)[source]
Django 1.11新增功能

销毁测试数据库,恢复预测试条件。

old_config is a data structure defining the changes in the database configuration that need to be reversed. 这是setup_databases()方法的返回值。

django.db.connection.creation

数据库后端的创建模块还提供了一些在测试过程中很有用的实用工具。

create_test_dbverbosity = 1autoclobber = Falseserialize = Truekeepdb = False )¶ T5>

创建一个新的测试数据库,并针对它运行migrate

verbosityrun_tests()具有相同的行为。

autoclobber描述如果发现与测试数据库同名的数据库,将会发生的行为:

  • 如果autoclobberFalse,则会要求用户批准销毁现有的数据库。 sys.exit is called if the user does not approve.
  • 如果autoclobber是True,数据库将被销毁,而不需要咨询用户。

serialize确定Django在运行测试之前是否将数据库序列化为内存中的JSON字符串(用于在没有事务的情况下在测试之间还原数据库状态)。 如果您没有任何具有serialized_rollback=True的测试类,则可以将其设置为False以加快创建时间。

如果您正在使用默认的测试运行器,则可以使用TEST字典中的SERIALIZE条目对其进行控制。

keepdb determines if the test run should use an existing database, or create a new one. 如果True,则将使用现有的数据库,或者如果不存在则创建。 如果False,将创建​​一个新的数据库,提示用户删除现有的数据库。

返回它创建的测试数据库的名称。

create_test_db()具有修改DATABASESNAME的值的副作用,以匹配测试数据库的名称。

destroy_test_dbold_database_nameverbosity = 1keepdb = False

销毁名为DATABASES中的NAME值的数据库,并将NAME设置为old_database_name的值。

verbosity参数具有与DiscoverRunner相同的行为。

如果keepdb参数是True,那么与数据库的连接将被关闭,但数据库不会被销毁。

coverage.py 集成

代码覆盖率描述了已经测试了多少源代码。 它显示了你的代码的哪些部分正在被测试执行,哪些不是。 这是测试应用程序的重要组成部分,因此强烈建议您检查测试的覆盖范围。

Django可以很容易地与coverage.py,一个测量Python程序的代码覆盖率的工具集成。 首先,安装coverage.py 接下来,从包含manage.py的项目文件夹中运行以下命令:

coverage run --source='.' manage.py test myapp

这将运行您的测试并收集项目中已执行文件的覆盖率数据。 您可以通过键入以下命令来查看此数据的报告:

coverage report

请注意,一些Django代码是在运行测试时执行的,但由于传递给前一个命令的source标志,因此这里没有列出。

有关更多选项(如详细说明错过的行的注释HTML列表),请参阅coverage.py文档。