Skip to main content

高级测试主题

请求工厂

class RequestFactory[源代码]

RequestFactory 与测试客户端共享相同的API。但是,RequestFactory不是像浏览器一样工作,而是提供了一种生成请求实例的方法,该请求实例可以用作任何视图的第一个参数。这意味着您可以像测试任何其他函数一样测试视图函数 - 作为黑盒子,具有完全已知的输入,测试特定的输出。

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

  • 它只能访问HTTP方法 get()post()put()delete()head()options()trace()

  • 这些方法接受所有相同的参数 exceptfollows。因为这只是一个生产请求的工厂,所以由您来处理响应。

  • 它不支持中间件。如果视图正常工作需要,会话和身份验证属性必须由测试本身提供。

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

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)

测试和多个数据库

测试主/副本配置

如果您使用主/副本(由某些数据库称为主/从属)测试多数据库配置,则创建测试数据库的这种策略会出现问题。创建测试数据库时,不会有任何复制,因此,在主节点上创建的数据将不会在副本上看到。

为了弥补这一点,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
    }
}

在这个设置中,我们有两个数据库服务器:dbprimary,由数据库别名 default 描述,dbreplica 由别名 replica 描述。如您所料,dbreplica 已被数据库管理员配置为 dbprimary 的只读副本,因此在正常活动中,default 的任何写入将显示在 replica 上。

如果Django创建了两个独立的测试数据库,这将破坏任何期望复制发生的测试。但是,replica 数据库已配置为测试镜像(使用 MIRROR 测试设置),表明在测试下,replica 应被视为 default 的镜像。

当配置测试环境时,将创建 replica 的测试版本 not。相反,到 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 数据库,因为它是唯一没有依赖关系的数据库别名。接下来将创建 defaultclubs 别名(尽管不保证创建该对的顺序),然后是 hearts,最后是 spades

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

TransactionTestCase 的高级功能

TransactionTestCase.available_apps

警告

此属性是专用API。它可能会在未来没有折旧期的情况下更改或删除,例如以适应应用程序加载的更改。

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

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

available_apps 设置为应用程序列表指示Django表现得好像只有来自这些应用程序的模型可用。 TransactionTestCase 的行为改变如下:

  • 在每次测试之前触发 post_migrate,以便为可用应用程序中的每个模型创建内容类型和权限,以防它们丢失。

  • 每次测试后,Django只清空与可用应用程序中的模型对应的表。但是,在数据库级别,截断可能级联到不可用应用程序中的相关模型。此外,post_migrate 不被解雇;它将被下一个 TransactionTestCase 触发,在选择正确的应用程序集之后。

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

因为 post_migrate 在刷新数据库后不会被释放,所以它在 TransactionTestCase 之后的状态与 TestCase 之后的状态不同:它缺少由侦听器创建的行到 post_migrate。考虑到 执行测试的顺序,这不是一个问题,只要给定测试套件中的所有 TransactionTestCase 都声明为 available_apps,或者它们都不声明。

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

TransactionTestCase.reset_sequences

TransactionTestCase 上设置 reset_sequences = True 将确保序列在测试运行之前总是复位:

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测试运行器测试可重用的应用程序

如果您正在编写 可重用的应用程序,您可能需要使用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测试运行器所需的最低限度。您可能需要添加用于控制详细程度的命令行选项,传递特定测试标签以运行等。

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

此文件包含运行应用程序测试所需的 Django设置

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

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

使用不同的测试框架

显然,unittest 不是唯一的Python测试框架。虽然Django不为替代框架提供显式支持,但它提供了一种方法来调用为替代框架构建的测试,就像他们是正常的Django测试一样。

当您运行 ./manage.py test 时,Django会查看 TEST_RUNNER 设置以确定要做什么。默认情况下,TEST_RUNNER 指向 'django.test.runner.DiscoverRunner'。这个类定义了默认的Django测试行为。这种行为包括:

  1. 执行全局预测试设置。

  2. 在名称与模式 test*.py 匹配的当前目录下的任何文件中查找测试。

  3. 创建测试数据库。

  4. 运行 migrate 以将模型和初始数据安装到测试数据库中。

  5. 运行发现的测试。

  6. 销毁测试数据库。

  7. 执行全局后测试拆卸。

如果您定义自己的测试运行器类并在该类指向 TEST_RUNNER,Django将在您运行 ./manage.py test 时执行测试运行器。这样,可以使用可以从Python代码执行的任何测试框架,或者修改Django测试执行过程以满足您可能遇到的任何测试需求。

定义测试运行器

测试运行器是定义 run_tests() 方法的类。 Django附带一个定义默认Django测试行为的 DiscoverRunner 类。此类定义了 run_tests() 入口点,以及 run_tests() 用来设置,执行和删除测试套件的其他方法的选择。

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

DiscoverRunner 将在与 pattern 匹配的任何文件中搜索测试。

top_level 可用于指定包含顶级Python模块的目录。通常Django可以自动计算出来,所以没有必要指定这个选项。如果指定,它通常应该是包含您的 manage.py 文件的目录。

verbosity 确定将打印到控制台的通知和调试信息量; 0 无输出,1 为正常输出,2 为详细输出。

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

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

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

如果 reverseTrue,则测试用例将以相反的顺序执行。这可能有助于调试未正确隔离且具有副作用的测试。使用此选项时将保留 按测试类分组

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

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

您的测试运行器还可以定义其他命令行选项。创建或覆盖 add_arguments(cls, parser) 类方法,并通过在方法中调用 parser.add_argument() 来添加自定义参数,以便 test 命令将能够使用这些参数。

属性

DiscoverRunner.test_suite

用于构建测试套件的类。默认情况下,它设置为 unittest.TestSuite。如果您希望实现用于收集测试的不同逻辑,则可以覆盖此选项。

DiscoverRunner.test_runner

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

DiscoverRunner.test_loader

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

方法

DiscoverRunner.run_tests(test_labels, extra_tests=None, **kwargs)[源代码]

运行测试套件。

test_labels 允许您指定运行哪些测试并支持多种格式(有关支持的格式列表,请参阅 DiscoverRunner.build_suite())。

extra_tests 是要添加到测试运行程序执行的套件的额外 TestCase 实例的列表。除了在 test_labels 中列出的模块中发现的那些之外,还运行这些额外的测试。

此方法应返回失败的测试数。

classmethod DiscoverRunner.add_arguments(parser)[源代码]

覆盖此类方法以添加 test 管理命令接受的自定义参数。有关向解析器添加参数的详细信息,请参阅 argparse.ArgumentParser.add_argument()

DiscoverRunner.setup_test_environment(**kwargs)[源代码]

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

DiscoverRunner.build_suite(test_labels, extra_tests=None, **kwargs)[源代码]

构造与提供的测试标签匹配的测试套件。

test_labels 是描述要运行的测试的字符串列表。测试标签可以采用以下四种形式之一:

  • 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 是要添加到测试运行程序执行的套件的额外 TestCase 实例的列表。除了在 test_labels 中列出的模块中发现的那些之外,还运行这些额外的测试。

返回一个准备运行的 TestSuite 实例。

DiscoverRunner.setup_databases(**kwargs)[源代码]

创建测试数据库。

返回一个数据结构,该结构提供足够的详细信息以撤销已进行的更改。这些数据将在测试结束时提供给 teardown_databases() 功能。

DiscoverRunner.run_suite(suite, **kwargs)[源代码]

运行测试套件。

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

DiscoverRunner.teardown_databases(old_config, **kwargs)[源代码]

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

old_config 是一种数据结构,用于定义数据库配置中需要反转的更改。它是 setup_databases() 方法的返回值。

DiscoverRunner.teardown_test_environment(**kwargs)[源代码]

恢复预测试环境。

DiscoverRunner.suite_result(suite, result, **kwargs)[源代码]

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

测试实用程序

django.test.utils

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

setup_test_environment()[源代码]

执行全局预测试设置,例如为模板呈现系统安装工具以及设置虚拟电子邮件发件箱。

teardown_test_environment()[源代码]

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

django.db.connection.creation

数据库后端的创建模块还提供了一些在测试期间可用的实用程序。

create_test_db(verbosity=1, autoclobber=False, serialize=True, keepdb=False)

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

verbosity 具有与 run_tests() 中相同的行为。

autoclobber 描述了在发现与测试数据库具有相同名称的数据库时将发生的行为:

  • 如果 autoclobberFalse,则将要求用户批准销毁现有数据库。如果用户不批准,则调用 sys.exit

  • 如果autoclobber是 True,数据库将被销毁,而不咨询用户。

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

如果使用默认测试运行器,可以使用 TEST 字典中的 SERIALIZE 条目来控制此测试。

keepdb 确定测试运行是否应使用现有数据库,或创建一个新的数据库。如果 True,将使用现有数据库,或如果不存在则创建。如果 False,将创建一个新的数据库,提示用户删除现有的数据库。

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

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

destroy_test_db(old_database_name, verbosity=1, keepdb=False)

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

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

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

coverage.py 集成

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

Django可以轻松地与 coverage.py 集成,这是一个用于测量Python程序代码覆盖率的工具。首先,install coverage.py。接下来,从包含 manage.py 的项目文件夹运行以下命令:

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

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

coverage report

请注意,一些Django代码在运行测试时已执行,但由于传递给上一个命令的 source 标志,因此未在此列出。

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