---
title: "单元测试集"
version: 6.0
locale: zh-hans
source: https://docs.djangoproject.com/zh-hans/6.0/internals/contributing/writing-code/unit-tests/
canonical: https://djangodocs.dev/zh-hans/6.0/internals/contributing/writing-code/unit-tests/
---
# 单元测试集

Django在代码基本库的 `tests` 目录中带有自己的测试套件。 我们的政策是确保所有测试始终通过。

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

Django 的测试都使用随 Django 一起提供的测试基础结构来测试应用程序。请查看 [编写并运行测试](/zh-hans/6.0/topics/testing/overview/) 以了解如何编写新的测试。

## 运行单元测试

### 快速上手

首先，在 GitHub 上 [fork Django](https://github.com/django/django/fork)。

第二步，创建并激活虚拟环境。如果你不熟悉如何做，可以阅读我们的 [贡献教程](/zh-hans/6.0/intro/contributing/)。

接下来，克隆您的 fork，安装一些要求，然后运行测试：

```console
$ 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
```

*Windows*

```doscon
...\> 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
```

安装这些要求可能需要一些操作系统包，您的计算机可能没有安装。通常，您可以通过搜索错误消息的最后一行或附近的信息来找出需要安装的包。如果需要的话，可以在搜索查询中添加您的操作系统信息。

If you have trouble installing an optional test dependency, you can skip that
dependency locally. For example, if installing `pylibmc` fails, comment out
its line in `requirements/py3.txt` and continue with `./runtests.py`.
Tests that require `pylibmc` will be skipped automatically. See
[运行所有测试](#running-unit-tests-dependencies) for details on installing the optional
test dependencies.

运行测试需要一个定义要使用的数据库的 Django 设置模块。为了帮助您入门，Django 提供并使用一个示例设置模块，使用 SQLite 数据库。请参阅 [使用另一个 settings 配置模块](#running-unit-tests-settings) 了解如何使用不同的设置模块以不同的数据库运行测试。

遇到问题了吗？请查看 [错误调试](#troubleshooting-unit-tests) 以了解一些常见问题。

### 使用 `tox` 运行测试

[Tox](https://tox.wiki/) 是一个在不同虚拟环境中运行测试的工具。Django 包含一个基本的 `tox.ini` 文件，用于自动执行一些我们的构建服务器在拉取请求上执行的检查。要运行单元测试和其他检查（如 [import sorting](/zh-hans/6.0/internals/contributing/writing-code/coding-style/#coding-style-imports)、[documentation spelling checker](/zh-hans/6.0/internals/contributing/writing-documentation/#documentation-spelling-check) 和 [code formatting](/zh-hans/6.0/internals/contributing/writing-code/coding-style/#coding-style-python)），请在 Django 源代码树的任何位置安装并运行 `tox` 命令：

```console
$ python -m pip install tox
$ tox
```

*Windows*

```doscon
...\> py -m pip install tox
...\> tox
```

By default, `tox` runs the test suite with the bundled test settings file for
SQLite, `black`, `blacken-docs`, `flake8`, `isort`, `lint-docs`,
`zizmor`, and the documentation spelling checker. In addition to the system
dependencies noted elsewhere in this documentation, the command `python3`
must be on your path and linked to the appropriate version of Python. A list of
default environments can be seen as follows:

```console
$ tox -l
py3
black
blacken-docs
flake8>=3.7.0
docs
isort>=7.0.0
lint-docs
zizmor>=1.16.3
```

*Windows*

```doscon
...\> tox -l
py3
black
blacken-docs
flake8>=3.7.0
docs
isort>=7.0.0
lint-docs
zizmor>=1.16.3
```

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

In addition to the default environments, `tox` supports running unit tests
for other versions of Python and other database backends. Since Django's test
suite doesn't bundle a settings file for database backends other than SQLite,
however, you must [create and provide your own test settings](#running-unit-tests-settings). For example, to run the tests on Python 3.12
using PostgreSQL:

```console
$ tox -e py312-postgres -- --settings=my_postgres_settings
```

*Windows*

```doscon
...\> tox -e py312-postgres -- --settings=my_postgres_settings
```

This command sets up a Python 3.12 virtual environment, installs Django's
test suite dependencies (including those for PostgreSQL), and calls
`runtests.py` with the supplied arguments (in this case,
`--settings=my_postgres_settings`).

本文档的其余部分展示了如何在没有 `tox` 的情况下运行测试的命令，但是，可以通过在参数列表前面加上 `--` 来将任何选项传递给 `tox`，就像上面一样。

`Tox` 还会尊重设置的 [`DJANGO_SETTINGS_MODULE`](/zh-hans/6.0/topics/settings/#envvar-DJANGO_SETTINGS_MODULE) 环境变量。例如，以下命令与上面的命令等效：

```console
$ DJANGO_SETTINGS_MODULE=my_postgres_settings tox -e py312-postgres
```

对于 Windows 用户应使用：

```doscon
...\> set DJANGO_SETTINGS_MODULE=my_postgres_settings
...\> tox -e py312-postgres
```

#### 运行 JavaScript 测试集

Django 包含了一组针对某些附加应用程序中的功能的 [JavaScript 单元测试](/zh-hans/6.0/internals/contributing/writing-code/javascript/#javascript-tests)。这些 JavaScript 测试默认情况下不会使用 `tox` 运行，因为它们需要安装 `Node.js`，并且对于大多数补丁来说并不是必需的。要使用 `tox` 运行 JavaScript 测试：

```console
$ tox -e javascript
```

*Windows*

```doscon
...\> tox -e javascript
```

这个命令运行 `npm install` 以确保测试要求是最新的，然后运行 `npm test`。

### 使用 `django-docker-box` 运行测试

[django-docker-box](https://github.com/django/django-docker-box/) 允许您在所有支持的数据库和 Python 版本上运行 Django 的测试套件。有关安装和使用说明，请参阅 [django-docker-box](https://github.com/django/django-docker-box/) 项目页面。

### 使用另一个 `settings` 配置模块

包含的设置模块（`tests/test_sqlite.py`）允许您使用 SQLite 运行测试套件。如果要使用不同的数据库运行测试，您需要定义自己的设置文件。一些测试，如 `contrib.postgres` 的测试，是特定于特定数据库后端的，如果使用不同的后端运行，则会被跳过。一些测试在特定的数据库后端上被跳过或预期失败（请查看每个后端上的 `DatabaseFeatures.django_test_skips` 和 `DatabaseFeatures.django_test_expected_failures`）。

要使用不同的设置运行测试，请确保该模块位于您的 [`PYTHONPATH`](https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH) 上，并使用 `--settings` 传递该模块。

在任何测试设置模块中，[`DATABASES`](/zh-hans/6.0/ref/settings/#std-setting-DATABASES) 设置需要定义两个数据库：

- 一个 `default` 数据库。该数据库应该使用您希望用于主要测试的后端。
- 一个带有别名 `other` 的数据库。`other` 数据库用于测试可以将查询定向到不同的数据库。该数据库应该使用与 `default` 相同的后端，但必须具有不同的名称。

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

- [`USER`](/zh-hans/6.0/ref/settings/#std-setting-USER) 选项需要指定数据库的现有用户帐户。该用户需要有执行 `CREATE DATABASE` 的权限，以便可以创建测试数据库。
- [`PASSWORD`](/zh-hans/6.0/ref/settings/#std-setting-PASSWORD) 配置选项需要为指定的配置 [`USER`](/zh-hans/6.0/ref/settings/#std-setting-USER) 提供密码。

测试数据库的名称是通过将 [`DATABASES`](/zh-hans/6.0/ref/settings/#std-setting-DATABASES) 中定义的数据库的 [`NAME`](/zh-hans/6.0/ref/settings/#std-setting-NAME) 设置的值前面添加 `test_` 来获取的。这些测试数据库在测试完成时被删除。

您还需要确保您的数据库使用 UTF-8 作为默认字符集。如果您的数据库服务器不使用 UTF-8 作为默认字符集，您需要在适用数据库的测试设置字典中为 [`CHARSET`](/zh-hans/6.0/ref/settings/#std-setting-TEST_CHARSET) 包含一个值。

### 执行一部分测试

Django的整个测试套件需要花一些时间才能运行，并且，例如，如果您刚刚向Django添加了一个想要快速运行而不运行其他所有功能的测试，则运行每个测试可能是多余的。您可以通过在命令行上将测试模块的名称附加到 `runtests.py` 上来运行单元测试的子集。

例如，如果您想仅运行有关通用关系和国际化的测试，请键入：

```console
$ ./runtests.py --settings=path.to.settings generic_relations i18n
```

*Windows*

```doscon
...\> runtests.py --settings=path.to.settings generic_relations i18n
```

如何找出各个测试的名称？查看 `tests/` \- 那里的每个目录名称都是一个测试的名称。

如果您只想运行特定类别的测试，可以指定一个包含单独测试类路径的列表。例如，要运行 `i18n` 模块的 `TranslationTests`，请输入：

```console
$ ./runtests.py --settings=path.to.settings i18n.tests.TranslationTests
```

*Windows*

```doscon
...\> runtests.py --settings=path.to.settings i18n.tests.TranslationTests
```

更进一步，您可以像这样指定一个单独的测试方法：

```console
$ ./runtests.py --settings=path.to.settings i18n.tests.TranslationTests.test_lazy_objects
```

*Windows*

```doscon
...\> runtests.py --settings=path.to.settings i18n.tests.TranslationTests.test_lazy_objects
```

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

```console
$ ./runtests.py --start-at=wsgi
```

*Windows*

```doscon
...\> runtests.py --start-at=wsgi
```

您还可以使用 `--start-after` 选项从指定的顶级模块之后开始运行测试。例如：

```console
$ ./runtests.py --start-after=wsgi
```

*Windows*

```doscon
...\> runtests.py --start-after=wsgi
```

请注意，`--reverse` 选项不影响 `--start-at` 或 `--start-after` 选项。而且，这些选项不能与测试标签一起使用。

### 运行 Selenium 测试

一些测试需要 Selenium 和一个 Web 浏览器。要运行这些测试，您必须安装 [selenium](https://pypi.org/project/selenium/) 包，并使用 `--selenium=<BROWSERS>` 选项运行测试。例如，如果您安装了 Firefox 和 Google Chrome：

```console
$ ./runtests.py --selenium=firefox,chrome
```

*Windows*

```doscon
...\> runtests.py --selenium=firefox,chrome
```

查看 [selenium.webdriver](https://github.com/SeleniumHQ/selenium/tree/trunk/py/selenium/webdriver) 包，了解可用浏览器的列表。

指定 `--selenium` 会自动设置 `--tags=selenium`，以仅运行需要 selenium 的测试。

一些浏览器（例如 Chrome 或 Firefox）支持无头测试，这可以更快速和更稳定。添加 `--headless` 选项以启用此模式。

#### Screenshot tests

为了测试对管理界面的更改，可以在启用 `--screenshots` 选项的情况下运行 selenium 测试。截图将保存到 `tests/screenshots/` 目录。

为了定义在 selenium 测试期间何时应截图，测试类必须使用 `@django.test.selenium.screenshot_cases` 装饰器，并附带支持的截图类型列表（`"desktop_size"`、`"mobile_size"`、`"small_screen_size"`、`"rtl"`、`"dark"` 和 `"high_contrast"`）。然后可以在所需位置调用 `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", "high_contrast"])
    def test_login_button_centered(self):
        self.selenium.get(self.live_server_url + reverse("admin:login"))
        self.take_screenshot("login")
        ...
```

这将生成登录页面的多个截图——一张用于桌面屏幕，一张用于移动屏幕，一张用于桌面上的从右到左语言，一张用于桌面上的暗模式，一张用于使用 chrome 时的桌面高对比度模式。

### 运行所有测试

如果要运行全套测试，则需要安装许多依赖项：

- [aiosmtpd](https://pypi.org/project/aiosmtpd/) 1.4.5+
- [argon2-cffi](https://pypi.org/project/argon2-cffi/) 23.1.0+
- [asgiref](https://pypi.org/project/asgiref/) 3.9.1+ (required)
- [bcrypt](https://pypi.org/project/bcrypt/) 4.1.1+
- [colorama](https://pypi.org/project/colorama/) 0.4.6+
- [docutils](https://pypi.org/project/docutils/) 0.22+
- [geoip2](https://pypi.org/project/geoip2/) 4.8.0+
- [Jinja2](https://pypi.org/project/Jinja2/) 2.11+
- [numpy](https://pypi.org/project/numpy/) 1.26.0+
- [Pillow](https://pypi.org/project/Pillow/) 10.1.0+
- [PyYAML](https://pypi.org/project/PyYAML/) 6.0.2+
- [pywatchman](https://pypi.org/project/pywatchman/)
- [redis](https://pypi.org/project/redis/) 5.1.0+
- [pymemcache](https://pypi.org/project/pymemcache/), plus a [supported Python binding](https://memcached.org/)
- [gettext](https://www.gnu.org/software/gettext/manual/gettext.html)
  ([Windows 上的 gettext](/zh-hans/6.0/topics/i18n/translation/#gettext-on-windows))
- [selenium](https://pypi.org/project/selenium/) 4.23.0+
- [sqlparse](https://pypi.org/project/sqlparse/) 0.5.0+ (required)
- [tblib](https://pypi.org/project/tblib/) 3.0.0+

你可以在 Django 源代码树的 `tests/requirements` 目录中找到这些依赖项的 [pip requirements 文件](https://pip.pypa.io/en/latest/user_guide/#requirements-files)，然后像这样安装它们：

```console
$ python -m pip install -r tests/requirements/py3.txt
```

*Windows*

```doscon
...\> py -m pip install -r tests\requirements\py3.txt
```

如果你在安装过程中遇到错误，你的系统可能缺少一个或多个 Python 包的依赖。请查阅失败软件包的文档，或者用你遇到的错误信息在网上搜索。

你还可以使用 `oracle.txt`、`mysql.txt` 或 `postgres.txt` 安装你选择的数据库适配器。

如果要测试 memcached 或 Redis 缓存后端，你还需要定义一个 [`CACHES`](/zh-hans/6.0/ref/settings/#std-setting-CACHES) 设置，分别指向你的 memcached 或 Redis 实例。

要运行 GeoDjango 测试，你需要 [设置一个空间数据库并安装地理空间库](/zh-hans/6.0/ref/contrib/gis/install/)。

这些依赖项中的每一个都是可选的。 如果您缺少其中的任何一个，则将跳过关联的测试。

要运行一些自动重新加载测试，你需要安装 [Watchman](https://facebook.github.io/watchman/) 服务。

### 代码覆盖率

鼓励贡献者对测试套件进行覆盖率测试，以确定需要额外测试的区域。在:ref:testing code coverage 1. 中介绍了coverage代码覆盖度工具的安装和使用。

要使用标准测试设置运行 Django 测试套件的覆盖率：

```console
$ coverage run ./runtests.py --settings=test_sqlite
```

*Windows*

```doscon
...\> coverage run runtests.py --settings=test_sqlite
```

在运行覆盖率后，通过运行以下命令合并所有覆盖率统计数据：

```console
$ coverage combine
```

*Windows*

```doscon
...\> coverage combine
```

然后通过运行以下命令生成 HTML 报告：

```console
$ coverage html
```

*Windows*

```doscon
...\> coverage html
```

运行Django测试的覆盖率时，随附的 `.coveragerc` 配置文件将 `coverage_html` 定义为报告的输出目录，并且还排除了与结果无关的几个目录（Django中包含的测试代码或外部代码）。

### Code coverage on pull requests

Django's continuous integration (CI) system automatically runs code coverage
analysis on pull requests and posts a comment with a diff coverage report. This
helps reviewers see which lines in the changed code are covered by tests.

**What the coverage report shows:**

The coverage report posted on pull requests uses [diff-cover](https://github.com/Bachmann1234/diff_cover) to analyze only
the lines that were changed or added in the PR. It shows:

- Lines that are covered by tests (✓)
- Lines that are not covered by tests (✗)
- Lines that cannot be covered (e.g., comments, blank lines)

**Important limitations:**

When reviewing coverage reports on pull requests, keep these limitations in
mind:

- **Database-specific code:** The CI coverage job runs tests using SQLite on
  Windows. Code paths specific to other databases (PostgreSQL, MySQL, Oracle)
  will appear as "not covered" even if database-specific tests exist. This is
  expected and acceptable.
- **Platform-specific code:** Similarly, code that only runs on certain
  operating systems (Linux, macOS) will appear as not covered when run on
  Windows.
- **Coverage doesn't equal quality:** A line being "covered" only means it was
  executed during tests. It doesn't guarantee the line is well-tested or that
  all edge cases are handled. During review, assess test quality beyond just
  coverage numbers.

Missing coverage should be considered a warning rather than a blocker and
should be evaluated in context.

## Contrib 应用程序

contrib 应用程序的测试可以在 [tests/](https://github.com/django/django/blob/stable/6.0.x/tests/) 目录中找到，通常位于 `<app_name>_tests` 下。例如，`contrib.auth` 的测试位于 [tests/auth\_tests](https://github.com/django/django/blob/stable/6.0.x/tests/auth_tests) 中。

## 错误调试

### 测试套件在 `main` 分支上出现挂起或失败。

确保你使用的是一个 [支持的 Python 版本](/zh-hans/6.0/faq/install/#faq-python-version-support) 的最新点发布版，因为早期版本中通常会有可能导致测试套件失败或挂起的错误。

在 **macOS** （High Sierra 和更新的版本）上，你可能会看到这个消息被记录下来，之后测试会挂起：

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

为了避免这个问题，设置一个 `OBJC_DISABLE_INITIALIZE_FORK_SAFETY` 环境变量，例如：

```shell
$ OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES ./runtests.py
```

或者将 `export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES` 添加到你的 shell 启动文件（例如 `~/.profile`）。

### 许多测试失败，出现了 `UnicodeEncodeError`。

如果未安装 `locales` 包，一些测试将会因为 `UnicodeEncodeError` 而失败。

你可以在基于 Debian 的系统上解决这个问题，例如，通过运行以下命令：

```console
$ apt-get install locales
$ dpkg-reconfigure locales
```

你可以在 macOS 系统上通过配置你的 shell 的 locale 来解决这个问题：

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

运行 `locale` 命令来确认更改。如果需要，可以将这些导出命令添加到你的 shell 启动文件（例如，对于 Bash，是 `~/.bashrc`）中，以避免需要重新输入它们。

### 仅在特定组合条件下失败的测试。

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

使用 `runtests.py` 的 `--bisect` 选项将在每次迭代中运行失败的测试，同时减半运行的测试集合，通常可以帮助识别与失败相关的少量测试。

例如，假设单独运行正常的失败测试是 `ModelTest.test_eq`，然后使用以下命令：

```console
$ ./runtests.py --bisect basic.tests.ModelTest.test_eq
```

*Windows*

```doscon
...\> runtests.py --bisect basic.tests.ModelTest.test_eq
```

将尝试确定与给定测试干扰的测试。首先，将使用测试套件的前半部分运行该测试。如果出现失败，将测试套件的前半部分分成两组，然后每组都将与指定的测试一起运行。如果在测试套件的前半部分没有失败，将使用指定的测试运行测试套件的后半部分，并根据前述方式进行适当的分割。该过程将重复，直到最小化失败测试的集合。

`--pair` 选项会将给定的测试与测试套件中的每个其他测试一起运行，让你检查是否有其他测试产生了导致失败的副作用。因此：

```console
$ ./runtests.py --pair basic.tests.ModelTest.test_eq
```

*Windows*

```doscon
...\> runtests.py --pair basic.tests.ModelTest.test_eq
```

将使用 `test_eq` 与每个测试标签配对。

使用 `--bisect` 和 `--pair`，如果你已经怀疑哪些情况可能导致失败，你可以在第一个标签之后通过 [指定更多的测试标签](#runtests-specifying-labels) 来限制进行交叉分析的测试。

```console
$ ./runtests.py --pair basic.tests.ModelTest.test_eq queries transactions
```

*Windows*

```doscon
...\> runtests.py --pair basic.tests.ModelTest.test_eq queries transactions
```

你还可以尝试使用 `--shuffle` 和 `--reverse` 选项以随机或反向顺序运行任何一组测试。这有助于验证以不同的顺序执行测试是否会导致任何问题：

```console
$ ./runtests.py basic --shuffle
$ ./runtests.py basic --reverse
```

*Windows*

```doscon
...\> runtests.py basic --shuffle
...\> runtests.py basic --reverse
```

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

如果你希望查看失败测试中运行的 SQL，你可以使用 `--debug-sql` 选项打开 [SQL 日志记录](/zh-hans/6.0/ref/logging/#django-db-logger)。如果将其与 `--verbosity=2` 结合使用，将输出所有的 SQL 查询：

```console
$ ./runtests.py basic --debug-sql
```

*Windows*

```doscon
...\> runtests.py basic --debug-sql
```

### 查看测试失败的完整回溯信息。

默认情况下，测试会并行运行，每个核心一个进程。然而，在并行运行测试时，对于任何测试失败，你只会看到一个截断的回溯信息。你可以使用 `--parallel` 选项来调整这个行为：

```console
$ ./runtests.py basic --parallel=1
```

*Windows*

```doscon
...\> runtests.py basic --parallel=1
```

你还可以使用 [`DJANGO_TEST_PROCESSES`](/zh-hans/6.0/ref/django-admin/#envvar-DJANGO_TEST_PROCESSES) 环境变量来实现这个目的。

## 编写测试的一些建议

### 隔离模型注册

为了避免污染全局的 :attr:\`~django.apps.apps 注册表并防止不必要的表创建，测试方法中定义的模型应该与临时的 `Apps` 实例绑定。要做到这一点，可以使用 [`isolate_apps()`](/zh-hans/6.0/topics/testing/tools/#django.test.utils.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`](/zh-hans/6.0/ref/models/options/#django.db.models.Options.app_label)，将自动分配给其所在测试类所在的应用程序的标签。
>
> 为了确保在 [`isolate_apps()`](/zh-hans/6.0/topics/testing/tools/#django.test.utils.isolate_apps) 实例的上下文中定义的模型被正确安装，你应该将目标的 `app_label` 集合作为参数传递：
>
> *`tests/app_label/tests.py`*
>
> ```python
> 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"
>
>         ...
> ```
