高级测试主题¶
请求工厂¶
-
class
RequestFactory
¶
RequestFactory
与测试客户端共享相同的 API。但是,RequestFactory 不会像浏览器那样工作,而是提供一种生成请求实例的方法,该实例可用作任何视图的第一个参数。这意味着你可以像测试任何其他函数一样测试视图函数——作为一个黑匣子,具有确切已知的输入,测试特定输出。
RequestFactory
的 API 是测试客户端 API 的一个稍受限制的子集
- 它只能访问 HTTP 方法
get()
、post()
、put()
、delete()
、head()
、options()
和trace()
。 - 这些方法接受所有相同的参数,除了
follow
。由于这只是用于生成请求的工厂,因此由你来处理响应。 - 它不支持中间件。如果视图要正常运行,则会话和身份验证属性必须由测试本身提供。
添加了 headers
参数。
示例¶
以下是使用请求工厂的单元测试
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
。
此类直接与 RequestFactory
API 兼容,唯一的区别在于它返回 ASGIRequest
实例,而不是 WSGIRequest
实例。它的所有方法仍然是同步可调用对象。
defaults
中的任意关键字参数直接添加到 ASGI 范围中。
添加了 headers
参数。
测试基于类的视图¶
为了在请求/响应周期之外测试基于类的视图,你必须确保它们已正确配置,方法是在实例化后调用 setup()
。
例如,假设有以下基于类的视图
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)
你可以通过首先实例化视图,然后将 request
传递给 setup()
,然后再继续执行测试代码,直接测试 get_context_data()
方法
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 时引发有用的错误消息。
测试和多个数据库¶
测试主/副本配置¶
如果您正在使用主/副本(某些数据库称为主/从)复制测试多数据库配置,则创建测试数据库的此策略会造成问题。创建测试数据库时,将不会进行任何复制,因此在主数据库上创建的数据不会显示在副本上。
为了弥补这一点,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
的测试版本。相反,与 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
数据库,因为它是唯一没有依赖关系的数据库别名。接下来将创建 default
和 clubs
别名(虽然不保证创建此对的顺序),然后是 hearts
,最后是 spades
。
如果在 DEPENDENCIES
定义中存在任何循环依赖关系,则会引发 ImproperlyConfigured
异常。
高级特性 TransactionTestCase
¶
-
TransactionTestCase.
available_apps
¶ 警告
此属性是私有 API。将来可能会在没有弃用期的情况下进行更改或删除,例如为了适应应用程序加载中的更改。
它用于优化 Django 自身的测试套件,其中包含数百个模型,但不同应用程序中的模型之间没有关系。
默认情况下,
available_apps
设置为None
。在每次测试后,Django 调用flush
来重置数据库状态。这将清空所有表并发出post_migrate
信号,该信号为每个模型重新创建一种内容类型和四个权限。此操作的开销与模型数量成正比。将
available_apps
设置为应用程序列表会指示 Django 仅当这些应用程序中的模型可用时才表现得像它们可用一样。TransactionTestCase
的行为会发生如下变化post_migrate
在每个测试之前触发,为 available apps 中的每个模型创建内容类型和权限(如果它们不存在)。- 在每个测试之后,Django 仅清空与 available apps 中的模型对应的表。但是,在数据库级别,截断可能会级联到 unavailable apps 中的相关模型。此外,
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.test.testcases.SerializeMixin
来顺序运行它们。此混入使用文件系统 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 测试运行器测试可重用应用程序¶
如果您正在编写 可重用应用程序,您可能希望使用 Django 测试运行器运行您自己的测试套件,从而受益于 Django 测试基础架构。
一种常见做法是在应用程序代码旁边创建一个 tests 目录,其结构如下
runtests.py
polls/
__init__.py
models.py
...
tests/
__init__.py
models.py
test_settings.py
tests.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 测试运行器所需的最低限度。您可能需要添加命令行选项来控制详细程度、传入要运行的特定测试标签等。
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 测试行为。此行为涉及
- 执行全局测试前设置。
- 在名称与模式
test*.py
匹配的当前目录下的任何文件中查找测试。 - 创建测试数据库。
- 运行
migrate
以将模型和初始数据安装到测试数据库中。 - 运行 系统检查。
- 运行找到的测试。
- 销毁测试数据库。
- 执行全局测试后清理。
如果你定义了自己的测试运行器类并将 TEST_RUNNER
指向该类,那么每当你运行 ./manage.py test
时,Django 都会执行你的测试运行器。通过这种方式,可以使用任何可以从 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
表示详细输出。如果
interactive
为True
,则测试套件在执行时有权向用户询问说明。此行为的一个示例是请求删除现有测试数据库的权限。如果interactive
为False
,则测试套件必须能够在没有任何人工干预的情况下运行。如果
failfast
为True
,则在检测到第一个测试失败后,测试套件将停止运行。如果
keepdb
为True
,则测试套件将使用现有数据库,或在必要时创建一个数据库。如果为False
,则将创建一个新数据库,提示用户删除现有数据库(如果存在)。如果
reverse
为True
,则测试用例将按相反的顺序执行。这对于调试未正确隔离且有副作用的测试很有用。按测试类分组 在使用此选项时保留。此选项可与--shuffle
结合使用,以针对特定随机种子反转顺序。debug_mode
指定在运行测试之前应将DEBUG
设置设置为哪一项。parallel
指定进程数。如果parallel
大于1
,测试套件将在parallel
进程中运行。如果测试用例类少于配置的进程,Django 将相应地减少进程数。每个进程都有自己的数据库。此选项需要第三方tblib
包才能正确显示回溯。tags
可用于指定一组 用于过滤测试的标签。可以与exclude_tags
结合使用。exclude_tags
可用于指定一组 用于排除测试的标签。可以与tags
结合使用。如果
debug_sql
为True
,则失败的测试用例会输出记录到 django.db.backends 记录器 的 SQL 查询以及回溯。如果verbosity
为2
,则输出所有测试中的查询。test_name_patterns
可用于指定一组模式,按名称过滤测试方法和类。如果
pdb
为True
,则将在每个测试错误或失败时生成一个调试器(pdb
或ipdb
)。如果
buffer
为True
,则会丢弃通过测试的输出。如果
enable_faulthandler
为True
,则会启用faulthandler
。如果
timing
为True
,则会显示测试时间,包括数据库设置和总运行时间。如果
shuffle
为一个整数,则在执行之前,会使用该整数作为随机种子,以随机顺序对测试用例进行混洗。如果shuffle
为None
,则会随机生成种子。在这两种情况下,种子都会被记录并在运行测试之前设置为self.shuffle_seed
。此选项可用于帮助检测未正确隔离的测试。按测试类分组 在使用此选项时会保留。logger
可用于传递 Python Logger 对象。如果提供了此对象,则会使用它来记录消息,而不是打印到控制台。Logger 对象会遵循其记录级别,而不是verbosity
。durations
会显示 N 个最慢测试用例的列表。将此选项设置为0
会导致显示所有测试的持续时间。需要 Python 3.12+。Django 可能会不时通过添加新参数来扩展测试运行器的功能。
**kwargs
声明允许进行此扩展。如果你对DiscoverRunner
进行子类化或编写自己的测试运行器,请确保它接受**kwargs
。您的测试运行器还可以定义其他命令行选项。创建或覆盖
add_arguments(cls, parser)
类方法,并在方法中调用parser.add_argument()
添加自定义参数,以便test
命令可以使用这些参数。Django 5.0 中的新增功能添加了
durations
参数。
属性¶
-
DiscoverRunner.
test_suite
¶ 用于构建测试套件的类。默认情况下,它设置为
unittest.TestSuite
。如果您希望实现不同的测试收集逻辑,则可以覆盖它。
-
DiscoverRunner.
test_runner
¶ 这是用于执行各个测试和格式化结果的低级测试运行器的类。默认情况下,它设置为
unittest.TextTestRunner
。尽管命名约定不幸相似,但这不是与DiscoverRunner
相同类型的类,后者涵盖了更广泛的职责。您可以覆盖此属性以修改测试的运行和报告方式。
-
DiscoverRunner.
test_loader
¶ 这是加载测试的类,无论是从 TestCase 还是模块,还是其他方式,并将它们捆绑到测试套件中以供运行器执行。默认情况下,它设置为
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()
并将DEBUG
设置为self.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
相匹配(见上文)。返回一个准备运行的
TestSuite
实例。
-
DiscoverRunner.
setup_databases
(**kwargs)¶ 通过调用
setup_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)¶ 根据测试套件和该测试套件的结果计算并返回一个返回代码。
测试实用工具¶
django.test.utils
¶
为了帮助创建您自己的测试运行器,Django 在 django.test.utils
模块中提供了一些实用方法。
-
setup_test_environment
(debug=None)¶ 执行全局测试前设置,例如安装模板渲染系统的检测工具和设置虚拟电子邮件收件箱。
如果
debug
不是None
,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
描述如果发现与测试数据库同名的数据库时将发生的行为- 如果
autoclobber
为False
,将要求用户批准销毁现有数据库。如果用户不批准,则调用sys.exit
。 - 如果
autoclobber
为True
,则将在不咨询用户的情况下销毁数据库。
serialize
确定 Django 是否在运行测试前将数据库序列化为内存中 JSON 字符串(用于在没有事务的情况下还原测试之间的数据库状态)。如果没有 serialized_rollback=True 的测试类,则可以将此设置为False
以加快创建速度。keepdb
确定测试运行应使用现有数据库还是创建一个新数据库。如果为True
,则将使用现有数据库,或在不存在时创建。如果为False
,则将创建一个新数据库,如果存在,则提示用户删除现有数据库。返回它创建的测试数据库的名称。
- 如果
-
destroy_test_db
(old_database_name, verbosity=1, keepdb=False)¶ 销毁数据库,其名称为
NAME
中DATABASES
的值,并将NAME
设置为old_database_name
的值。verbosity
参数的行为与DiscoverRunner
相同。如果
keepdb
参数为True
,则将关闭与数据库的连接,但不会销毁数据库。
与 coverage.py
集成¶
代码覆盖率描述了已测试的源代码数量。它显示了测试正在执行代码的哪些部分,以及哪些部分没有执行。这是测试应用程序的重要组成部分,因此强烈建议检查测试的覆盖率。
Django 可以轻松地与 coverage.py 集成,coverage.py 是用于测量 Python 程序代码覆盖率的工具。首先,安装 coverage。接下来,从包含 manage.py
的项目文件夹中运行以下内容
coverage run --source='.' manage.py test myapp
这将运行测试并收集项目中已执行文件的覆盖率数据。你可以通过键入以下命令来查看此数据的报告
coverage report
请注意,在运行测试时执行了一些 Django 代码,但由于传递给上一个命令的 source
标志,因此此处未列出这些代码。
有关更多选项,例如详细说明未命中行的带注释的 HTML 列表,请参阅 coverage.py 文档。