高级测试主题

请求工厂

class RequestFactory[源代码]

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

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

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

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

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

Changed in Django Development version:

这个 query_params 参数已添加。

例子

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

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

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)

AsyncRequestFactory

class AsyncRequestFactory[源代码]

RequestFactory 创建类似WSGI的请求。如果您想创建类似ASGI的请求,包括拥有一个正确的ASGI scope ,您可以改为使用 django.test.AsyncRequestFactory .

此类与直接API兼容 RequestFactory ,唯一不同的是它返回 ASGIRequest 实例而不是 WSGIRequest 实例。它的所有方法仍然是同步可调用的。

中的任意关键字参数 defaults 直接添加到ASGI作用域。

Changed in Django Development version:

这个 query_params 参数已添加。

测试基于类的视图

为了在请求/响应周期之外测试基于类的视图,必须通过调用 setup() 实例化后。

例如,假设以下基于类的视图:

views.py
from django.views.generic import TemplateView


class HomeView(TemplateView):
    template_name = "myapp/home.html"

    def get_context_data(self, **kwargs):
        kwargs["environment"] = "Production"
        return super().get_context_data(**kwargs)

你可以直接测试 get_context_data() 方法,首先实例化视图,然后传递 requestsetup() ,在继续测试代码之前:

tests.py
from django.test import RequestFactory, TestCase
from .views import HomeView


class HomePageTest(TestCase):
    def test_environment_set_in_context(self):
        request = RequestFactory().get("/")
        view = HomeView()
        view.setup(request)

        context = view.get_context_data()
        self.assertIn("environment", context)

测试和多个主机名

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

支持多租户或基于请求的主机以其他方式更改业务逻辑并在测试中使用自定义主机名的项目必须将这些主机包括在 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/",
            headers={"host": "docs.djangoproject.dev:8000"},
        )
        self.assertEqual(response.status_code, 200)

设置文件包括项目支持的域列表:

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

另一个选项是将所需主机添加到 ALLOWED_HOSTS 使用 override_settings()modify_settings() . 对于无法打包自己的设置文件的独立应用程序或域列表不是静态的项目(例如,用于多租户的子域),此选项可能更可取。例如,您可以为域编写一个测试 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,测试客户端将不会引发有用的错误消息。

测试和多个数据库

测试主/副本配置

如果使用主/副本(某些数据库称为master/slave)复制测试多数据库配置,则创建测试数据库的这种策略会带来问题。创建测试数据库时,不会有任何复制,因此,在主数据库上创建的数据在副本上看不到。

为了弥补这一点,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 ,由数据库别名描述 defaultdbreplica 由别名描述 replica . 如你所料, dbreplica 已由数据库管理员配置为的读取副本 dbprimary ,所以在正常活动中,任何写入 default 将出现在 replica .

如果Django创建了两个独立的测试数据库,这将破坏任何预期会发生复制的测试。然而, replica 数据库已配置为测试镜像(使用 MIRROR 测试设置),表示正在测试, replica 应该像镜子一样 default .

配置测试环境后,测试版本 replica 将要 not 被创造出来。相反,连接到 replica 将被重定向到指向 default 。因此,写入到 default 将出现在 replica --而是因为它们实际上是同一个数据库,而不是因为这两个数据库之间存在数据复制。由于这取决于事务,因此测试必须使用 TransactionTestCase 而不是 TestCase

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

默认情况下,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 和A后面的不一样吗? TestCase :缺少侦听器创建的行 post_migrate . 考虑到 order in which tests are executed ,这也不是问题,只要 TransactionTestCase 在给定的测试套件中声明 available_apps 或者没有。

available_apps 在Django自己的测试套件中是必需的。

TransactionTestCase.reset_sequences

设置 reset_sequences = True 在一 TransactionTestCase 将确保在测试运行之前始终重置序列::

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.test.testcases.SerializeMixin 按顺序运行它们。这个mixin使用文件系统 lockfile .

例如,您可以使用 __file__ 确定同一文件中继承 SerializeMixin 将按顺序运行:

import os

from django.test import TestCase
from django.test.testcases import SerializeMixin


class ImageTestCaseMixin(SerializeMixin):
    lockfile = __file__

    def setUp(self):
        self.filename = os.path.join(temp_storage_dir, "my_file.png")
        self.file = create_file(self.filename)


class RemoveImageTests(ImageTestCaseMixin, TestCase):
    def test_remove_image(self):
        os.remove(self.filename)
        self.assertFalse(os.path.exists(self.filename))


class ResizeImageTests(ImageTestCaseMixin, TestCase):
    def test_resize_image(self):
        resize_image(self.file, (48, 48))
        self.assertEqual(get_image_size(self.file), (48, 48))

使用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测试运行程序所需的最低限度。您可能需要添加命令行选项来控制冗长性、传递要运行的特定测试标签等。

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

此文件包含 Django settings 需要运行应用程序的测试。

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

自从 测验 包包含在 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. 运行 system checks .

  6. 运行找到的测试。

  7. 正在销毁测试数据库。

  8. 正在执行全局测试后拆卸。

如果您定义自己的测试运行程序类和点 TEST_RUNNER 在那节课上,Django将在每次运行时执行测试运行程序 ./manage.py test . 这样,就可以使用任何可以从python代码执行的测试框架,或者修改django测试执行过程以满足您可能拥有的任何测试需求。

定义测试运行程序

测试运行器是定义 run_tests() 方法。Django出货时配备了一台 DiscoverRunner 定义默认Django测试行为的类。此类定义了 run_tests() 入口点,外加一组由 run_tests() 来设置、执行和拆除测试套件。

class DiscoverRunner(pattern='test*.py', top_level=None, verbosity=1, interactive=True, failfast=False, keepdb=False, reverse=False, debug_mode=False, debug_sql=False, parallel=0, tags=None, exclude_tags=None, test_name_patterns=None, pdb=False, buffer=False, enable_faulthandler=True, timing=True, shuffle=False, logger=None, durations=None, **kwargs)[源代码]

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

top_level 可用于指定包含顶级python模块的目录。通常,Django可以自动解决这个问题,所以不需要指定这个选项。如果指定,则通常应该是包含 manage.py 文件。

verbosity 确定将打印到控制台的通知和调试信息的数量; 0 没有输出, 1 是正常输出,并且 2 是详细输出。

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

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

如果 keepdbTrue 测试套件将使用现有数据库,或者在必要时创建一个数据库。如果 False 将创建一个新数据库,提示用户删除现有数据库(如果存在)。

如果 reverseTrue ,测试用例将以相反的顺序执行。这对于调试没有正确隔离且有副作用的测试可能很有用。 Grouping by test class 使用此选项时将保留。此选项可与一起使用 --shuffle 来颠倒特定随机种子的顺序。

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

parallel 指定进程数。如果 parallel 大于 1 ,测试套件将在 parallel 流程。如果测试用例类少于配置的进程,Django将相应地减少进程的数量。每个进程都有自己的数据库。此选项需要第三方 tblib 包以正确显示回溯。

tags 可用于指定一组 tags for filtering tests 。可以与 exclude_tags

exclude_tags 可用于指定一组 tags for excluding tests 。可以与 tags

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

test_name_patterns 可用于指定一组模式,以按名称筛选测试方法和类。

如果 pdbTrue ,调试器 (pdbipdb )将在每次测试错误或失败时生成。

如果 bufferTrue ,将丢弃通过测试的输出。

如果 enable_faulthandlerTruefaulthandler 将启用。

如果 timingTrue ,将显示测试计时,包括数据库设置和总运行时间。

如果 shuffle 是一个整数,则测试用例将在执行之前以随机顺序打乱,并使用该整数作为随机种子。如果 shuffleNone ,种子将随机生成。在这两种情况下,种子都将被记录并设置为 self.shuffle_seed 在运行测试之前。此选项可用于帮助检测未正确隔离的测试。 Grouping by test class 使用此选项时将保留。

logger 可以用来传递一个Python Logger object 。如果提供,记录器将用于记录消息,而不是打印到控制台。记录器对象将遵循其日志记录级别,而不是 verbosity

durations 将显示N个最慢测试用例的列表。将此选项设置为 0 将导致显示所有测试的持续时间。需要使用Python3.12+。

Django可能会不时地通过添加新参数来扩展测试运行程序的功能。这个 **kwargs 声明允许此扩展。如果你是子类 DiscoverRunner 或者编写自己的测试运行程序,确保它接受 **kwargs .

测试运行程序还可以定义其他命令行选项。创建或重写 add_arguments(cls, parser) 类方法并通过调用 parser.add_argument() 在方法内部,这样 test 命令将能够使用这些参数。

New in Django 5.0:

这个 durations 添加了参数。

属性

DiscoverRunner.test_suite

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

DiscoverRunner.test_runner

这是低级测试运行程序的类,用于执行单个测试并格式化结果。默认设置为 unittest.TextTestRunner . 尽管命名约定中存在不幸的相似性,但这与 DiscoverRunner 包括一系列更广泛的责任。可以重写此属性以修改运行和报告测试的方式。

DiscoverRunner.test_loader

这是一个类,它从测试用例或模块或其他地方加载测试,并将它们打包到测试套件中,供运行程序执行。默认设置为 unittest.defaultTestLoader . 如果您的测试将以不寻常的方式加载,则可以重写此属性。

方法

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

运行测试套件。

test_labels 允许您指定要运行的测试并支持多种格式(请参见 DiscoverRunner.build_suite() 获取支持格式的列表)。

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

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

重写此类方法以添加 test 管理命令。见 argparse.ArgumentParser.add_argument() 有关向分析器添加参数的详细信息。

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

通过调用设置测试环境 setup_test_environment() 设置 DEBUGself.debug_mode (默认为 False

DiscoverRunner.build_suite(test_labels=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 (见上文)。

返回A TestSuite 实例已准备好运行。

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

通过调用 setup_databases() .

DiscoverRunner.run_checks(databases)[源代码]

运行 system checks 在测试中 databases .

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

运行测试套件。

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

DiscoverRunner.get_test_runner_kwargs()[源代码]

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

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

销毁测试数据库,通过调用 teardown_databases() .

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

恢复预测试环境。

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

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

DiscoverRunner.log(msg, level=None)[源代码]

如果一个 logger ,则以给定整数记录消息。 logging level (例如: logging.DEBUGlogging.INFO ,或 logging.WARNING )。否则,消息将打印到控制台,并与当前 verbosity 。例如,不会打印消息,如果 verbosity 为0, INFO 和以上将被打印,如果 verbosity 至少为1,并且 DEBUG 如果至少为2,则将被打印。 level 默认为 logging.INFO

测试实用程序

django.test.utils

为了帮助创建自己的测试运行程序,Django在 django.test.utils 模块。

setup_test_environment(debug=None)[源代码]

执行全局预测试设置,例如安装模板呈现系统的检测和设置虚拟电子邮件发件箱。

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

teardown_test_environment()[源代码]

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

setup_databases(verbosity, interactive, *, time_keeper=None, keepdb=False, debug_sql=False, parallel=0, aliases=None, serialized_aliases=None, **kwargs)[源代码]

创建测试数据库。

返回提供足够详细信息以撤消所做更改的数据结构。此数据将提供给 teardown_databases() 测试结束时的功能。

这个 aliases 参数确定哪一个 DATABASES 应为其设置别名测试数据库。如果未提供,则默认为所有 DATABASES 别名。

这个 serialized_aliases 参数确定哪些子集 aliases 测试数据库应将其状态序列化,以允许使用 serialized_rollback 特写。如果未提供,则默认为 aliases

teardown_databases(old_config, parallel=0, keepdb=False)[源代码]

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

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

django.db.connection.creation

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

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

创建新的测试数据库并运行 migrate 反对它。

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

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

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

  • 如果 autoclobberTrue ,数据库将在未咨询用户的情况下被销毁。

serialize 确定Django在运行测试之前是否将数据库序列化为内存中的JSON字符串(如果没有事务,则用于在测试之间还原数据库状态)。你可以把这个设置为 False 如果没有任何测试类, serialized_rollback=True .

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

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

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

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

销毁名称为值的数据库 NAME 在里面 DATABASES 和集合 NAME 的价值 old_database_name .

这个 verbosity 参数的行为与for相同 DiscoverRunner .

如果 keepdb 论证是 True ,则将关闭与数据库的连接,但不会破坏数据库。

与集成 coverage.py

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

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

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

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

coverage report

注意,一些django代码是在运行测试时执行的,但由于 source 传递给上一个命令的标志。

有关更多选项,如带批注的HTML列表,详细说明缺少的行,请参见 coverage.py 博士学位。