Skip to main content

单元测试

Django自带了一个测试套件,在代码库的 tests 目录中。这是我们的政策,以确保所有测试在任何时候通过。

我们赞赏任何和所有的贡献的测试套件!

Django测试都使用Django附带的测试基础结构来测试应用程序。有关如何编写新测试的说明,请参阅 编写和运行测试

运行单元测试

快速开始

首先,fork Django on GitHub

第二,创建和激活虚拟环境。如果你不熟悉如何做,阅读我们的 贡献教程

接下来,克隆你的fork,安装一些需求,并运行测试:

$ git clone git@github.com:YourGitHubName/django.git django-repo
$ cd django-repo/tests
$ pip install -e ..
$ pip install -r requirements/py3.txt  # Python 2: py2.txt
$ ./runtests.py

安装需求可能需要您的计算机尚未安装的某些操作系统软件包。通常通过对错误消息的最后一行执行Web搜索,您可以找出要安装的软件包。如果需要,请尝试将您的操作系统添加到搜索查询中。

如果您在安装需求时遇到问题,可以跳过该步骤,但在Python 2上必须使用 pip install mock。有关安装可选测试依赖关系的详细信息,请参阅 运行所有测试。如果没有安装可选依赖项,那么将跳过需要它的测试。

运行测试需要一个Django设置模块来定义要使用的数据库。为了便于开始,Django提供并使用了使用SQLite数据库的示例设置模块。请参阅 使用另一个 settings 模块 了解如何使用不同的设置模块来使用不同的数据库运行测试。

Windows用户

我们建议使用 Git Bash 来运行上述方法的测试。

有问题吗?有关常见问题,请参阅 故障排除

使用另一个 settings 模块

包含的设置模块(tests/test_sqlite.py)允许您使用SQLite运行测试套件。如果要使用不同的数据库运行测试,则需要定义自己的设置文件。某些测试(例如 contrib.postgres 的测试)特定于特定数据库后端,如果使用不同的后端运行,将会跳过。

要使用不同的设置运行测试,请确保模块在 PYTHONPATH 上,并将模块传递给 --settings

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

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

  • 具有别名 other 的数据库。 other 数据库用于测试查询可以定向到不同的数据库。此数据库应使用与 default 相同的后端,并且它必须具有不同的名称。

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

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

  • PASSWORD 选项需要为指定的 USER 提供密码。

测试数据库通过将 test_ 添加到在 DATABASES 中定义的数据库的 NAME 设置的值来获得它们的名称。测试完成后,将删除这些测试数据库。

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

只运行一些测试

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

例如,如果要仅对通用关系和国际化运行测试,请键入:

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

你如何找到个别测试的名称?看看在 tests/ - 每个目录名称有一个测试的名称。

如果只想运行特定类的测试,可以指定单个测试类的路径列表。例如,要运行 i18n 模块的 TranslationTests,请键入:

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

除此之外,你可以像这样指定一个单独的测试方法:

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

运行Selenium测试

有些测试需要Selenium和Web浏览器。要运行这些测试,必须安装 selenium 软件包,并使用 --selenium=<BROWSERS> 选项运行测试。例如,如果您安装了Firefox和Google Chrome浏览器:

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

有关可用浏览器的列表,请参阅 selenium.webdriver 软件包。

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

运行所有测试

如果你想运行全套测试,你需要安装一些依赖:

您可以在Django源代码树的 tests/requirements 目录中的 pip requirements files 中找到这些依赖项,并像这样安装它们:

$ pip install -r tests/requirements/py3.txt  # Python 2: py2.txt

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

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

如果要测试memcached缓存后端,还需要定义一个指向memcached实例的 CACHES 设置。

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

这些依赖关系中的每一个都是可选的。如果你缺少任何一个,相关的测试将被跳过。

代码覆盖率

鼓励贡献者在测试套件上运行覆盖以识别需要额外测试的区域。 testing code coverage 中描述了覆盖工具的安装和使用。

覆盖应该在单个进程中运行以获得准确的统计信息。使用标准测试设置运行Django测试套件上的coverage:

$ coverage run ./runtests.py --settings=test_sqlite --parallel=1

运行coverage后,通过运行生成html报告:

$ coverage html

当运行Django测试的coverage时,包括的 .coveragerc 设置文件将 coverage_html 定义为报告的输出目录,并且排除与结果(测试代码或Django中包含的外部代码)不相关的几个目录。

Contrib应用程序

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

故障排除

许多测试失败与 UnicodeEncodeError

如果未安装 locales 软件包,则某些测试将失败,并显示 UnicodeEncodeError

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

$ apt-get install locales
$ dpkg-reconfigure locales

你可以通过配置shell的locale来解决这个问题:

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

运行 locale 命令确认更改。或者,将这些导出命令添加到您的shell启动文件(例如,用于Bash的 ~/.bashrc),以避免重新键入它们。

仅在组合中失败的测试

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

runtests.py--bisect 选项将运行失败的测试,同时将测试集减半,它与每次迭代一起运行,通常可以识别可能与失败相关的少量测试。

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

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

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

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

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

test_eq 与每个测试标签配对。

使用 --bisect--pair,如果您已经怀疑哪些情况可能导致故障,您可以限制测试在第一个测试后被 指定进一步的测试标签 交叉分析:

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

您也可以尝试使用 --reverse 选项反向运行任何一组测试,以验证以不同顺序执行测试不会导致任何故障:

$ ./runtests.py basic --reverse

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

如果希望检查在失败测试中运行的SQL,可以使用 --debug-sql 选项打开 SQL日志。如果将此与 --verbosity=2 组合,将输出所有SQL查询:

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

查看测试失败的完全追溯

默认情况下,测试与每个核心的一个进程并行运行。但是,当测试并行运行时,您只会看到任何测试失败的截断跟踪。您可以使用 --parallel 选项调整此行为:

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

您还可以将 DJANGO_TEST_PROCESSES 环境变量用于此目的。

New in Django 1.9:

支持并行运行测试和 --parallel 选项。

写测试的提示

隔离模型注册

为了避免污染全局 apps 注册表并防止创建不必要的表,测试方法中定义的模型应绑定到临时 Apps 实例:

from django.apps.registry import Apps
from django.db import models
from django.test import SimpleTestCase

class TestModelDefinition(SimpleTestCase):
    def test_model_definition(self):
        test_apps = Apps(['app_label'])

        class TestModel(models.Model):
            class Meta:
                apps = test_apps
        ...
django.test.utils.isolate_apps(*app_labels, attr_name=None, kwarg_name=None)
New in Django 1.10.

由于这种模式涉及很多样板,Django提供了 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'
        ...

装饰器也可以应用到类:

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

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

用于隔离模型注册的临时 Apps 实例可以在通过使用 attr_name 参数用作类装饰器时作为属性检索:

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

@isolate_apps('app_label', attr_name='apps')
class TestModelDefinition(SimpleTestCase):
    def test_model_definition(self):
        class TestModel(models.Model):
            pass
        self.assertIs(self.apps.get_model('app_label', 'TestModel'), TestModel)

或者作为测试方法的参数,当通过使用 kwarg_name 参数作为方法装饰器使用时:

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

class TestModelDefinition(SimpleTestCase):
    @isolate_apps('app_label', kwarg_name='apps')
    def test_model_definition(self, apps):
        class TestModel(models.Model):
            pass
        self.assertIs(apps.get_model('app_label', 'TestModel'), TestModel)