Unit tests

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

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

Django测试使用Django附带的测试基础架构来测试应用程序。 有关如何编写新测试的说明,请参见Writing and running tests

Running the unit tests

Quickstart

首先,在gitHub上的fork Django

其次,创建并激活一个虚拟环境。 如果您不熟悉如何做,请阅读我们的contributing tutorial

接下来,克隆你的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 安装 mock 有关安装可选测试依赖关系的详细信息,请参阅Running all the tests 如果您没有安装可选的依赖关系,则会跳过需要的测试。

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

Windows用户

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

有问题吗? 有关常见问题,请参阅Troubleshooting

使用tox 运行测试

Tox是用于在不同虚拟环境中运行测试的工具。 Django包括一个基本的tox.ini,可以自动执行我们的构建服务器对拉请求执行的一些检查。 To run the unit tests and other checks (such as import sorting, the documentation spelling checker, and code formatting), install and run the tox command from any place in the Django source tree:

$ pip install tox
$ tox

默认情况下,tox运行带有SQLite捆绑测试设置文件的测试套件,flake8isort以及文档拼写检查器。 除了本文档中其他地方所指定的系统依赖性之外,命令python2python3必须位于您的路径上,并链接到相应的Python版本。 默认环境列表可以看出如下:

$ tox -l
py3
flake8
docs
isort

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

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

$ tox -e py35-postgres -- --settings=my_postgres_settings

该命令设置一个Python 3.5虚拟环境,安装Django的测试套件依赖项(包括PostgreSQL的依赖项),并使用提供的参数调用runtests.py(在这种情况下,--settings=my_postgres_settings

本文档的其余部分显示了运行没有tox的测试的命令,但是传递给runtests.py的任何选项也可以传递到tox前缀参数列表与--,如上。

Tox还尊重DJANGO_SETTINGS_MODULE环境变量,如果设置。 例如,以下内容与上述命令相同:

$ DJANGO_SETTINGS_MODULE=my_postgres_settings tox -e py35-postgres

运行JavaScript测试

Django在某些contrib应用程序中包含一组JavaScript unit tests JavaScript测试在默认情况下不使用tox运行,因为它们需要安装Node.js,并且绝大多数补丁都不需要。 使用tox运行JavaScript测试:

$ tox -e javascript

该命令运行npm install,以确保测试要求是最新的,然后运行npm 测试 T5> T3>。

Using another settings module

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

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

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

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

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

  • USER选项需要指定数据库的现有用户帐户。 该用户需要执行CREATE DATABASE的权限,才能创建测试数据库。
  • PASSWORD选项需要为指定的USER提供密码。

测试数据库通过在DATABASES中定义的数据库的NAME设置的值之前加上test_ 测试完成后,将删除这些测试数据库。

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

Running only some of the tests

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

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

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

如何找出个别测试的名称? tests/中查看 - 每个目录名都有一个测试的名称。

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

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

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

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

Running the Selenium tests

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

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

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

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

Running all the tests

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

您可以在Django源代码树的tests/requirements目录中的pip需求文件中找到这些依赖关系,并安装它们,如下所示:

$ 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

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

Code coverage

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

覆盖范围应在单一过程中运行,以获得准确的统计数据。 要使用标准测试设置在Django测试套件上运行覆盖率:

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

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

$ coverage html

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

Contrib apps

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

Troubleshooting

Many test failures with UnicodeEncodeError

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

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

$ apt-get install locales
$ dpkg-reconfigure locales

您可以通过配置您的shell的区域设置来解决macOS系统的问题:

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

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

Tests that only fail in combination

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

--bisectruntests.py选项将运行失败的测试,同时将测试集减半,它在每次迭代中一起运行,通常可以识别一个小可能与故障相关的测试次数。

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

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

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

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

$ ./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

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

$ ./runtests.py basic --reverse

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

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

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

看到测试失败的完整回溯

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

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

您也可以使用DJANGO_TEST_PROCESSES环境变量。

Tips for writing tests

隔离型号注册

为了避免污染全球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。isolation_apps* app_labelsattr_name = Nonekwarg_name = None
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的集合作为参数传递:

测试/ 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)