编写和运行测试

此文档分为两个主要部分。首先,我们解释如何用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"')

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

For more details about unittest ,请参见python文档。

测试应该在哪里进行?

默认值 startapp 模板创建 tests.py 新应用程序中的文件。如果您只有几个测试,这可能很好,但是随着测试套件的增长,您可能希望将其重组为一个测试包,以便将测试拆分为不同的子模块,例如 test_models.pytest_views.pytest_forms.py 等等。随便选择你喜欢的组织方案。

也见 使用django测试运行程序测试可重用的应用程序 .

警告

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

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

运行试验

编写完测试后,使用 test 控制您的项目 manage.py 实用程序:

$ ./manage.py test

测试发现基于单元测试模块的 built-in test discovery 。默认情况下,这将发现名为的任何文件中的测试 test*.py 在当前工作目录下。

提供任意数量的“测试标签”,以指定要运行的特定测试 ./manage.py test 。每个测试标签可以是指向包、模块 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 class
$ ./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 测试运行时,测试运行程序将等待当前运行的测试完成,然后优雅地退出。在正常退出期间,测试运行程序将输出任何测试失败的详细信息,报告运行了多少测试以及遇到了多少错误和失败,并像往常一样销毁任何测试数据库。如此迫切 Ctrl-C 如果你忘了通过 --failfast 选项,请注意,有些测试意外失败,希望在不等待完整测试运行完成的情况下获取有关失败的详细信息。

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

启用警告的测试

最好在启用了python警告的情况下运行测试: python -Wa manage.py test . 这个 -Wa 标志指示python显示取消预测警告。Django和其他许多Python库一样,在特性消失时使用这些警告来标记。它还可能会在代码中标记一些并非完全错误的区域,但可以从更好的实现中获益。

测试数据库

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

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

您可以使用 test --keepdb 选择权。这将在运行之间保留测试数据库。如果数据库不存在,将首先创建它。还将应用任何迁移,以使其保持最新。

如前一节所述,如果测试运行被强制中断,则测试数据库可能不会被破坏。在下一次运行时,将询问您是否要重用或销毁数据库。使用 test --noinput 选项以取消该提示并自动销毁数据库。这在在连续集成服务器上运行测试时很有用,例如,在该服务器上,测试可能被超时中断。

默认的测试数据库名称是通过预先准备创建的。 test_ 到每个的值 NAME 在里面 DATABASES . 使用sqlite时,测试将默认使用内存中的数据库(即,数据库将在内存中创建,完全绕过文件系统!). The TEST 词典在 DATABASES 提供了许多配置测试数据库的设置。例如,如果要使用其他数据库名称,请指定 NAMETEST 中任何给定数据库的字典 DATABASES .

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

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

要对测试数据库的字符编码进行细粒度控制,请使用 CHARSET 测试选项。如果您使用的是MySQL,也可以使用 COLLATION 选项控制测试数据库使用的特定排序规则。见 settings documentation 有关这些和其他高级设置的详细信息。

如果将SQLite内存数据库与SQLite一起使用, shared cache 已启用,因此您可以编写具有在线程之间共享数据库功能的测试。

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

如果代码在编译其模块时试图访问数据库,则会发生这种情况。 之前 测试数据库已设置,可能会出现意外结果。例如,如果您在模块级代码中有一个数据库查询,并且存在一个真正的数据库,那么生产数据可能会污染您的测试。 It is a bad idea to have such import-time database queries in your code 无论如何-重写代码,这样它就不会这样做。

这也适用于 ready() .

执行测试的顺序

为了保证 TestCase 代码从一个干净的数据库开始,Django测试运行程序按以下方式重新排序测试:

  • 所有 TestCase 子类首先运行。

  • 然后,所有其他基于Django的测试(基于 SimpleTestCase ,包括 TransactionTestCase )运行时,它们之间没有特定的顺序保证或强制。

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

备注

新的测试顺序可能会显示对测试用例顺序的意外依赖性。这是基于给定的 TransactionTestCase 测试时,必须更新它们才能独立运行。

备注

在加载测试时检测到的故障排序在上述所有操作之前,以便更快地进行反馈。这包括无法找到或由于语法错误而无法加载的测试模块。

可以在组内随机化和/或颠倒执行顺序。 test --shuffle--reverse 选择。这有助于确保您的测试彼此独立。

回滚仿真

迁移中加载的任何初始数据只能在 TestCase 测试而不是 TransactionTestCase 测试,并且仅在支持事务的后端(最重要的异常是myisam)。这对于依赖于 TransactionTestCaseLiveServerTestCaseStaticLiveServerTestCase .

Django可以通过设置 serialized_rollback 选择权 True 在尸体里 TestCaseTransactionTestCase 但请注意,这将使该测试套件的速度减慢约3倍。

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

初始序列化通常非常快,但是如果您希望从这个过程中排除一些应用程序(并稍微加速测试运行),您可以将这些应用程序添加到 TEST_NON_SERIALIZED_APPS .

要防止两次加载序列化数据,请设置 serialized_rollback=True 禁用 post_migrate 刷新测试数据库时发出信号。

其他试验条件

无论 DEBUG 在配置文件中设置,所有Django测试都使用 DEBUG =假。这是为了确保观察到的代码输出与将在生产设置中看到的内容相匹配。

在每次测试和运行后不会清除缓存 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 .

密码哈希

默认密码散列器的设计速度相当慢。如果在测试中对许多用户进行身份验证,则可能需要使用自定义设置文件并设置 PASSWORD_HASHERS 设置为更快的哈希算法:

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

别忘了也包括在 PASSWORD_HASHERS 夹具中使用的任何散列算法(如果有)。

保留测试数据库

这个 test --keepdb 选项在测试运行之间保留测试数据库。它跳过创建和销毁操作,这可以大大缩短运行测试的时间。

避免对媒体文件进行磁盘访问

这个 InMemoryStorage 是防止对媒体文件进行磁盘访问的一种便捷方式。所有数据都保存在内存中,然后在测试运行后被丢弃。