单元测试

Django自带了一套测试套件, tests 代码库的目录。我们的政策是确保所有测试始终通过。

我们感谢您对测试套件的所有贡献!

Django测试都使用Django附带的测试基础设施来测试应用程序。见 编写和运行测试 关于如何编写新测试的解释。

运行单元测试

快速启动

第一, fork Django on GitHub .

第二,创建并激活虚拟环境。如果您不熟悉如何操作,请阅读我们的 contributing tutorial .

接下来,克隆fork,安装一些需求,然后运行测试:

$ git clone https://github.com/YourGitHubName/django.git django-repo
$ cd django-repo/tests
$ python -m pip install -e ..
$ python -m pip install -r requirements/py3.txt
$ ./runtests.py
...\> git clone https://github.com/YourGitHubName/django.git django-repo
...\> cd django-repo\tests
...\> py -m pip install -e ..
...\> py -m pip install -r requirements\py3.txt
...\> runtests.py 

安装这些要求可能需要一些您的计算机尚未安装的操作系统包。您通常可以通过在Web上搜索错误消息的最后一行来确定要安装哪个包。如果需要,尝试将您的操作系统添加到搜索查询中。

如果在安装需求时遇到问题,可以跳过这一步。见 运行所有测试 有关安装可选测试依赖项的详细信息。如果没有安装可选的依赖项,则将跳过需要它的测试。

运行测试需要一个定义要使用的数据库的Django设置模块。为了帮助您入门,Django提供并使用了一个使用SQLite数据库的示例设置模块。看到了吗 使用另一个 settings 模块 了解如何使用不同的设置模块使用不同的数据库运行测试。

有问题吗?见 故障排除 一些常见问题。

运行测试使用 tox

Tox 是用于在不同虚拟环境中运行测试的工具。Django包括一个基本的 tox.ini 这会自动执行我们的构建服务器对拉请求执行的一些检查。要运行单元测试和其他检查(如 import sorting vt.的. documentation spelling checker ,以及 code formatting ),安装并运行 tox 来自Django源代码树中任何位置的命令:

$ python -m pip install tox
$ tox
...\> py -m pip install tox
...\> tox

默认情况下, tox 运行带有捆绑的SQLite测试设置文件的测试套件, blackblacken-docsflake8isort 和文档拼写检查器。除了本文档中其他地方提到的系统依赖项外,命令 python3 必须位于您的路径上,并链接到适当版本的Python。默认环境列表如下所示:

$ tox -l
py3
black
blacken-docs
flake8>=3.7.0
docs
isort>=5.1.0
...\> tox -l
py3
black
blacken-docs
flake8>=3.7.0
docs
isort>=5.1.0

测试其他python版本和数据库后端

除了默认环境之外, tox 支持为其他版本的Python和其他数据库后端运行单元测试。然而,由于Django的测试套件不捆绑除SQLite之外的数据库后端的设置文件,因此您必须 create and provide your own test settings 。例如,要使用PostgreSQL在Python3.10上运行测试:

$ tox -e py310-postgres -- --settings=my_postgres_settings
...\> tox -e py310-postgres -- --settings=my_postgres_settings

此命令设置一个Python3.10虚拟环境,安装Django的测试套件依赖项(包括用于PostgreSQL的依赖项),并调用 runtests.py 使用提供的参数(在本例中, --settings=my_postgres_settings )。

本文档的其余部分显示了在 tox 但是,任何选项都传递给 runtests.py 也可以传递给 tox 通过在参数列表前加前缀 -- ,如上所述。

Tox 也尊重 DJANGO_SETTINGS_MODULE 环境变量(如果设置)。例如,以下命令等同于上面的命令:

$ DJANGO_SETTINGS_MODULE=my_postgres_settings tox -e py310-postgres

Windows用户应使用:

...\> set DJANGO_SETTINGS_MODULE=my_postgres_settings
...\> tox -e py310-postgres

运行javascript测试

Django包括一套 JavaScript unit tests 用于某些控件应用程序中的函数。默认情况下不会使用 tox 因为他们需要 Node.js 安装,大多数补丁不需要。要运行javascript测试,请使用 tox

$ tox -e javascript
...\> tox -e javascript

此命令运行 npm install 确保测试需求是最新的,然后运行 npm test .

运行测试使用 django-docker-box

django-docker-box 允许您跨所有受支持的数据库和python版本运行Django的测试套件。见 django-docker-box 安装和使用说明的项目页。

使用另一个 settings 模块

包含的设置模块 (tests/test_sqlite.py )允许您使用SQLite运行测试套件。如果要使用不同的数据库运行测试,则需要定义您自己的设置文件。一些测试,例如用于 contrib.postgres 是特定于特定数据库后端的,如果使用不同的后端运行,将被跳过。特定数据库后端上的某些测试被跳过或预期会失败(请参见 DatabaseFeatures.django_test_skipsDatabaseFeatures.django_test_expected_failures 在每个后端上)。

要使用不同的设置运行测试,请确保模块位于 PYTHONPATH 通过模块 --settings .

这个 DATABASES 任何测试设置模块中的设置都需要定义两个数据库:

  • A default 数据库。此数据库应使用要用于主测试的后端。

  • 具有别名的数据库 other . 这个 other 数据库用于测试查询是否可以定向到不同的数据库。此数据库应使用与 default ,它必须有一个不同的名称。

如果您使用的后端不是sqlite,则需要为每个数据库提供其他详细信息:

  • 这个 USER 选项需要指定数据库的现有用户帐户。该用户需要权限才能执行 CREATE DATABASE 以便可以创建测试数据库。

  • 这个 PASSWORD 选项需要提供 USER 已指定。

测试数据库通过预先准备获取名称 test_ 价值 NAME 中定义的数据库的设置 DATABASES . 测试完成后,这些测试数据库将被删除。

您还需要确保数据库使用UTF-8作为默认字符集。如果数据库服务器不使用utf-8作为默认字符集,则需要包含 CHARSET 在适用数据库的测试设置字典中。

只运行一些测试

Django的整个测试套件需要一段时间才能运行,并且如果您只是在Django中添加了一个您希望在不运行其他所有测试的情况下快速运行的测试,那么运行每个测试可能是多余的。通过将测试模块的名称附加到 runtests.py 在命令行上。

例如,如果只想为泛型关系和国际化运行测试,请键入:

$ ./runtests.py --settings=path.to.settings generic_relations i18n
...\> runtests.py --settings=path.to.settings generic_relations i18n

如何找出各个测试的名称?拜访 tests/ -每个目录名都有一个测试的名称。

如果您只想执行特定类别的测试,您可以指定个别测试类别的路径清单。例如,运行 TranslationTestsi18n 模块,类型:

$ ./runtests.py --settings=path.to.settings i18n.tests.TranslationTests
...\> runtests.py --settings=path.to.settings i18n.tests.TranslationTests

除此之外,您还可以指定一个单独的测试方法,如下所示:

$ ./runtests.py --settings=path.to.settings i18n.tests.TranslationTests.test_lazy_objects
...\> runtests.py --settings=path.to.settings i18n.tests.TranslationTests.test_lazy_objects

您可以使用从指定的顶级模块开始运行测试 --start-at 选择权。例如:

$ ./runtests.py --start-at=wsgi
...\> runtests.py --start-at=wsgi

也可以在指定的顶层模块之后使用 --start-after 选择权。例如:

$ ./runtests.py --start-after=wsgi
...\> runtests.py --start-after=wsgi

请注意 --reverse 选项不影响 --start-at--start-after 选项。此外,这些选项不能与测试标签一起使用。

运行硒测试

有些测试需要硒和网络浏览器。要运行这些测试,您必须安装 selenium 打包并使用 --selenium=<BROWSERS> 选择。例如,如果您安装了Firefox和Google Chrome:

$ ./runtests.py --selenium=firefox,chrome
...\> runtests.py --selenium=firefox,chrome

selenium.webdriver 可用浏览器列表的包。

指定 --selenium 自动设置 --tags=selenium 只运行需要硒的测试。

一些浏览器(如Chrome或Firefox)支持无头测试,这样可以更快、更稳定。添加 --headless 启用此模式的选项。

为了测试对管理用户界面的更改,可以使用 --screenshots 选项已启用。屏幕截图将保存到 tests/screenshots/ 目录。

要定义在Selify测试期间何时应该截取屏幕截图,测试类必须使用 @django.test.selenium.screenshot_cases 具有支持的屏幕截图类型列表的修饰器 ("desktop_size""mobile_size""small_screen_size""rtl" ,以及 "dark" )。然后,它可以调用 self.take_screenshot("unique-screenshot-name") 在所需的点生成屏幕截图。例如::

from django.test.selenium import SeleniumTestCase, screenshot_cases
from django.urls import reverse


class SeleniumTests(SeleniumTestCase):
    @screenshot_cases(["desktop_size", "mobile_size", "rtl", "dark"])
    def test_login_button_centered(self):
        self.selenium.get(self.live_server_url + reverse("admin:login"))
        self.take_screenshot("login")
        ...

这将生成多个登录页面的屏幕截图-一个用于桌面屏幕,一个用于移动屏幕,一个用于桌面上从右向左的语言,还有一个用于桌面上的黑暗模式。

Changed in Django Development version:

这个 --screenshots 选项和 @screenshot_cases 添加了装饰师。

运行所有测试

如果要运行完整的测试套件,则需要安装一些依赖项:

您可以在中找到这些依赖项 pip requirements files 内部 tests/requirements Django源代码树的目录并按如下方式安装它们:

$ python -m pip install -r tests/requirements/py3.txt
...\> py -m pip install -r tests\requirements\py3.txt

如果在安装过程中遇到错误,您的系统可能缺少一个或多个Python包的依赖项。请参考失败包的文档或使用您遇到的错误消息在Web上搜索。

您也可以使用安装您选择的数据库适配器 oracle.txtmysql.txtpostgres.txt .

如果您想测试Memcached或Redis缓存后端,您还需要定义 CACHES 设置分别指向您的Memcached或Redis实例。

要运行GeoDjango测试,您需要 set up a spatial database and install the Geospatial libraries

每个依赖项都是可选的。如果您丢失了其中的任何一个,则将跳过相关的测试。

要运行某些自动重新加载测试,您需要安装 Watchman 服务。

代码覆盖率

鼓励参与者在测试套件上运行覆盖范围,以确定需要额外测试的区域。覆盖工具的安装和使用在 testing code coverage .

要使用标准测试设置在Django测试套件上运行覆盖:

$ coverage run ./runtests.py --settings=test_sqlite
...\> coverage run runtests.py --settings=test_sqlite

运行Coverage后,通过运行以下命令合并所有Coverage统计信息:

$ coverage combine
...\> coverage combine

之后,通过运行以下命令生成html报告:

$ coverage html
...\> coverage html

当运行Django测试的覆盖范围时,包括 .coveragerc 设置文件定义 coverage_html 作为报告的输出目录,也排除了与结果无关的几个目录(测试代码或Django中包含的外部代码)。

控制应用程序

Contrrib应用程序的测试可在 tests/ 目录,通常位于 <app_name>_tests 。例如,测试 contrib.auth 位于 tests/auth_tests

故障排除

测试套件挂起或显示故障 main 支部

确保您拥有 supported Python version ,因为早期版本中经常存在可能导致测试套件失败或挂起的错误。

在……上面 macOS (较高的Sierra和更新版本),您可能会看到记录此消息,在此之后测试挂起:

objc[42074]: +[__NSPlaceholderDate initialize] may have been in progress in
another thread when fork() was called.

要避免这种设置,请使用 OBJC_DISABLE_INITIALIZE_FORK_SAFETY 环境变量,例如:

$ OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES ./runtests.py

或添加 export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES 到shell的启动文件(例如。 ~/.profile

许多测试失败 UnicodeEncodeError

如果 locales 未安装包,某些测试将失败 UnicodeEncodeError .

您可以在基于Debian的系统上解决此问题,例如,通过运行:

$ apt-get install locales
$ dpkg-reconfigure locales

您可以通过配置shell的语言环境来解决此问题:

$ export LANG="en_US.UTF-8"
$ export LC_ALL="en_US.UTF-8"

运行 locale 命令确认更改。或者,将这些导出命令添加到shell的启动文件(例如 ~/.bashrc 为了避免重打。

仅组合失败的测试

如果一个测试在单独运行时通过,但在整个套件中失败,那么我们有一些工具来帮助分析问题。

这个 --bisect 选择权 runtests.py 将运行失败的测试,同时将在每次迭代中与之一起运行的测试集减半,通常使识别可能与失败相关的少量测试成为可能。

例如,假设独立工作的失败测试是 ModelTest.test_eq ,然后使用:

$ ./runtests.py --bisect basic.tests.ModelTest.test_eq
...\> runtests.py --bisect basic.tests.ModelTest.test_eq

将尝试确定干扰给定测试的测试。首先,使用测试套件的前半部分运行测试。如果发生故障,测试套件的前半部分将分为两组,然后使用指定的测试运行每个组。如果测试套件的前半部分没有失败,则使用指定的测试运行测试套件的后半部分,并按照前面的描述适当地拆分。该过程将重复,直到将失败的测试集最小化。

这个 --pair 选项将给定测试与套件中的每个其他测试一起运行,让您可以检查另一个测试是否有导致失败的副作用。所以:

$ ./runtests.py --pair basic.tests.ModelTest.test_eq
...\> runtests.py --pair basic.tests.ModelTest.test_eq

威尔对 test_eq 每个测试标签。

两者兼有 --bisect--pair ,如果您已经怀疑哪些情况可能是导致失败的原因,您可以将测试限制为 specifying further test labels 在第一个之后:

$ ./runtests.py --pair basic.tests.ModelTest.test_eq queries transactions
...\> runtests.py --pair basic.tests.ModelTest.test_eq queries transactions

方法以随机或相反的顺序运行任何一组测试。 --shuffle--reverse 选择。这有助于验证以不同的顺序执行测试不会导致任何问题:

$ ./runtests.py basic --shuffle
$ ./runtests.py basic --reverse
...\> runtests.py basic --shuffle
...\> runtests.py basic --reverse

查看测试期间运行的SQL查询

如果要检查在失败测试中运行的SQL,可以打开 SQL logging 使用 --debug-sql 选择权。如果你把这个和 --verbosity=2 ,将输出所有SQL查询:

$ ./runtests.py basic --debug-sql
...\> runtests.py basic --debug-sql

看到测试失败的完整回溯

默认情况下,测试与每个核心一个进程并行运行。然而,当测试并行运行时,您只会看到任何测试失败的截断的回溯。您可以使用 --parallel 选项:

$ ./runtests.py basic --parallel=1
...\> runtests.py basic --parallel=1

您也可以使用 DJANGO_TEST_PROCESSES 用于此目的的环境变量。

写测试提示

隔离模型注册

避免污染全球环境 apps 注册表并防止不必要的表创建,测试方法中定义的模型应绑定到临时 Apps 举个例子。为此,请使用 isolate_apps() 装饰师::

from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps


class TestModelDefinition(SimpleTestCase):
    @isolate_apps("app_label")
    def test_model_definition(self):
        class TestModel(models.Model):
            pass

        ...

设置 app_label

测试方法中定义的没有显式的模型 app_label 将自动为其测试类所在的应用程序分配标签。

为了确保在 isolate_apps() 实例安装正确,应通过目标 app_label 作为参数:

tests/app_label/tests.py
from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps


class TestModelDefinition(SimpleTestCase):
    @isolate_apps("app_label", "other_app_label")
    def test_model_definition(self):
        # This model automatically receives app_label='app_label'
        class TestModel(models.Model):
            pass

        class OtherAppModel(models.Model):
            class Meta:
                app_label = "other_app_label"

        ...