Skip to main content

编写和运行测试

本文档分为两个主要部分。首先,我们解释如何使用Django编写测试。然后,我们解释如何运行它们。

写测试

Django单元测试使用一个Python标准库模块:unittest。该模块使用基于类的方法定义测试。

这里是一个从 django.test.TestCase 子类的示例,它是 unittest.TestCase 的子类,在事务中运行每个测试以提供隔离:

from django.test import TestCase
from myapp.models import Animal

class AnimalTestCase(TestCase):
    def setUp(self):
        Animal.objects.create(name="lion", sound="roar")
        Animal.objects.create(name="cat", sound="meow")

    def test_animals_can_speak(self):
        """Animals that can speak are correctly identified"""
        lion = Animal.objects.get(name="lion")
        cat = Animal.objects.get(name="cat")
        self.assertEqual(lion.speak(), 'The lion says "roar"')
        self.assertEqual(cat.speak(), 'The cat says "meow"')

当您使用 运行你的测试 时,测试实用程序的默认行为是在名称以 test 开头的任何文件中查找所有测试用例(即 unittest.TestCase 的子类),自动从这些测试用例构建测试套件,并运行该套件。

有关 unittest 的更多详细信息,请参阅Python文档。

测试应该在哪里生活?

默认的 startapp 模板在新应用程序中创建 tests.py 文件。这可能是好的,如果你只有几个测试,但随着你的测试套件的增长,你可能想重组它到一个测试包,所以你可以将测试分成不同的子模块,如 test_models.pytest_views.pytest_forms.py 等。Feel随意选择你喜欢的组织方案。

参见 使用Django测试运行器测试可重用的应用程序

警告

如果您的测试依赖于数据库访问,例如创建或查询模型,请确保将测试类创建为 django.test.TestCase 的子类,而不是 unittest.TestCase

使用 unittest.TestCase 避免了在事务中运行每个测试和刷新数据库的成本,但是如果测试与数据库交互,它们的行为将根据测试运行器执行它们的顺序而变化。这可能导致单元测试在孤立运行时传递,但在套件中运行时失败。

运行测试

一旦你写了测试,运行它们使用项目的 manage.py 实用程序的 test 命令:

$ ./manage.py test

测试发现基于unittest模块的 内置测试发现。默认情况下,这将在当前工作目录下名为“test * .py”的任何文件中发现测试。

您可以通过向 ./manage.py test 提供任意数量的“测试标签”来指定要运行的特定测试。每个测试标签可以是完整的Python虚线路径到包,模块,TestCase 子类或测试方法。例如:

# Run all the tests in the animals.tests module
$ ./manage.py test animals.tests

# Run all the tests found within the 'animals' package
$ ./manage.py test animals

# Run just one test case
$ ./manage.py test animals.tests.AnimalTestCase

# Run just one test method
$ ./manage.py test animals.tests.AnimalTestCase.test_animals_can_speak

您还可以提供目录的路径,以发现该目录下的测试:

$ ./manage.py test animals/

您可以指定使用 -p (或 --pattern)选项,自定义文件名模式匹配,如果你的测试文件是从 test*.py 模式命名不同:

$ ./manage.py test --pattern="tests_*.py"

如果在测试运行时按 Ctrl-C,测试运行器将等待当前运行的测试完成,然后正常退出。在正常退出期间,测试运行程序将输出任何测试失败的详细信息,报告运行了多少测试以及遇到了多少错误和失败,并像往常一样销毁任何测试数据库。因此,如果忘记传递 --failfast 选项,请注意一些测试意外失败,并想要获取有关失败的详细信息,而不等待完整的测试运行完成,那么按 Ctrl-C 可能非常有用。

如果您不想等待当前运行的测试完成,您可以再次按 Ctrl-C,测试运行将立即停止,但不会正常。不会报告在中断之前运行的测试的详细信息,并且将不会销毁由运行创建的任何测试数据库。

测试时启用警告

最好在启用Python警告的情况下运行测试:python -Wall manage.py test-Wall 标志告诉Python显示弃用警告。 Django和许多其他Python库一样,使用这些警告来标记特性何时消失。它也可能标记你的代码中的区域,不是严格错误,但可以从一个更好的实现中受益。

测试数据库

需要数据库的测试(即模型测试)不会使用您的“真实”(生产)数据库。为测试创建单独的空白数据库。

无论测试通过还是失败,当所有测试都被执行时,测试数据库将被销毁。

您可以使用 test --keepdb 选项防止测试数据库被破坏。这将在运行之间保留测试数据库。如果数据库不存在,它将首先被创建。任何迁移也将应用,以保持最新。

默认测试数据库名称是通过将 test_ 添加到 DATABASES 中每个 NAME 的值来创建的。当使用SQLite时,测试将默认使用内存数据库(即,数据库将在内存中创建,完全绕过文件系统)。 DATABASES 中的 TEST 字典提供了一些配置测试数据库的设置。例如,如果要使用不同的数据库名称,请在 TEST 字典中为 DATABASES 中的任何给定数据库指定 NAME

在PostgreSQL上,USER 还需要对内置 postgres 数据库的读取访问。

除了使用单独的数据库,否则测试运行器将使用您在设置文件中具有的所有相同的数据库设置:ENGINEUSERHOST 等。测试数据库由 USER 指定的用户创建,需要确保给定的用户帐户有足够的权限在系统上创建新的数据库。

要对测试数据库的字符编码进行细粒度控制,请使用 CHARSET TEST选项。如果您使用MySQL,还可以使用 COLLATION 选项来控制测试数据库使用的特定归类。有关这些和其他高级设置的详细信息,请参阅 设置文档

如果使用带有Python 3.4+和SQLite 3.7.13+的SQLite内存数据库,共享缓存 将被启用,因此您可以编写具有在线程之间共享数据库的能力的测试。

在运行测试时从生产数据库中查找数据?

如果您的代码在编译模块时尝试访问数据库,则会出现 before 测试数据库设置,可能出现意外结果。例如,如果您在模块级代码中有数据库查询,并且存在真实数据库,生产数据可能会污染您的测试。 在你的代码中有这样的导入时数据库查询是一个坏主意 反正 - 重写你的代码,使它不这样做。

这也适用于 ready() 的定制实现。

执行测试的顺序

为了保证所有 TestCase 代码以干净的数据库开始,Django测试运行器以下列方式重新排序测试:

  • 所有 TestCase 子类首先运行。

  • 然后,所有其他基于Django的测试(基于 SimpleTestCase 的测试用例,包括 TransactionTestCase)运行时没有保证也不强制执行的特定顺序。

  • 然后运行可能更改数据库而不将其恢复到其原始状态的任何其他 unittest.TestCase 测试(包括doctest)。

注解

测试的新排序可能揭示对测试用例排序的意外依赖。这是依赖于通过给定的 TransactionTestCase 测试依赖于数据库中的状态的doctests的情况,它们必须被更新以能够独立地运行。

您可以使用 test --reverse 选项反转组中的执行顺序。这可以帮助确保您的测试彼此独立。

回滚仿真

迁移中加载的任何初始数据只能在 TestCase 测试中使用,而不能在 TransactionTestCase 测试中使用,此外只能在支持事务的后端(最重要的例外是MyISAM)上使用。这也适用于依赖于 TransactionTestCase 的测试,例如 LiveServerTestCaseStaticLiveServerTestCase

Django可以通过在 TestCaseTransactionTestCase 的正文中将 serialized_rollback 选项设置为 True 来为每个测试用例重新加载该数据,但请注意,这将使该测试套件减慢大约3倍。

第三方应用程序或针对MyISAM开发的应用程序需要进行设置;但是,一般来说,您应该针对事务数据库开发自己的项目,并且在大多数测试中使用 TestCase,因此不需要此设置。

初始序列化通常很快,但如果您希望从此过程中排除某些应用程序(并加快测试运行速度),您可以将这些应用程序添加到 TEST_NON_SERIALIZED_APPS

Changed in Django 1.9.

为了防止序列化数据被加载两次,设置 serialized_rollback=True 在刷新测试数据库时禁用 post_migrate 信号。

其他测试条件

无论配置文件中的 DEBUG 设置的值如何,所有Django测试都运行 DEBUG = False。这是为了确保您的代码的观察输出与生产设置中将看到的匹配。

缓存不会在每次测试后清除,如果您在生产环境中运行测试,运行“manage.py test fooapp”可以将测试中的数据插入到实时系统的缓存中,因为与数据库不同,单独的“测试缓存”不是用过的。这种行为 may change 在未来。

了解测试输出

当您运行测试时,您将看到一些消息,因为测试运行器准备自己。您可以使用命令行上的 verbosity 选项控制这些消息的详细程度:

Creating test database...
Creating table myapp_animal
Creating table myapp_mineral

这告诉你测试运行器正在创建一个测试数据库,如上一节所述。

一旦创建了测试数据库,Django将运行你的测试。如果一切顺利,你会看到这样的东西:

----------------------------------------------------------------------
Ran 22 tests in 0.221s

OK

但是,如果有测试失败,您将看到有关哪些测试失败的完整详细信息:

======================================================================
FAIL: test_was_published_recently_with_future_poll (polls.tests.PollMethodTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dev/mysite/polls/tests.py", line 16, in test_was_published_recently_with_future_poll
    self.assertIs(future_poll.was_published_recently(), False)
AssertionError: True is not False

----------------------------------------------------------------------
Ran 1 test in 0.003s

FAILED (failures=1)

此错误输出的完整解释超出了本文档的范围,但是它非常直观。有关详细信息,请参阅Python的 unittest 库文档。

请注意,对于任何数量的失败和错误测试,测试运行程序脚本的返回码为1。如果所有测试通过,返回代码为0.如果您在shell脚本中使用测试运行程序脚本并需要在该级别上测试成功或失败,则此功能非常有用。

加快测试速度

并行运行测试

只要您的测试正确隔离,就可以并行运行它们,以便在多核硬件上获得更快的速度。见 test --parallel

密码散列

默认密码hasher设计相当慢。如果您在测试中验证许多用户,您可能需要使用自定义设置文件,并将 PASSWORD_HASHERS 设置设置为更快的哈希算法:

PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.MD5PasswordHasher',
]

不要忘记还在 PASSWORD_HASHERS 中包括用于灯具的任何散列算法(如果有的话)。