写作和运行测试

本文档分为2个主要单元。 首先,我们讲解如何利用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,自动从这些测试用例中构建测试套件,并运行该套件。

更多关于 unittest的细节,请参阅 Python文档。

测试应该在哪里生活?

默认的startapp模板在新应用程序中创建一个tests.py文件。 This might be fine if you only have a few tests, but as your test suite grows you’ll likely want to restructure it into a tests package so you can split your tests into different submodules such as test_models.py, test_views.py, test_forms.py, etc. 随时选择您喜欢的组织方案。

另请参见Using the Django test runner to test reusable applications

警告

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

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

运行测试

完成测试后,请使用您的项目manage.py实用程序的test命令运行它们:

$ ./manage.py test

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

您可以通过向./ manage.py 测试提供任意数量的“测试标签”来指定要运行的特定测试。 每个测试标签可以是一个完整的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 (或者test*.py)选项来指定一个文件, 如果你的测试文件名不是 --pattern 构成的

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

如果正在测试的时候按下了 Ctrl-C , 测试程序会等待当前正在运行的测试完毕之后再退出。 在正常退出期间,测试运行程序将输出任何测试失败的详细信息,报告运行了多少测试以及遇到了多少错误和失败,并像往常一样销毁任何测试数据库。 因此,如果您忘记通过--failfast选项,则按Ctrl-C可能会非常有用,请注意,某些测试意外失败,并希望获得有关故障的详细信息,等待完整的测试运行完成。

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

测试时启用警告

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

测试数据库

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

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

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

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

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

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

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

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

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

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

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

执行测试的顺序

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

  • 所有TestCase子类首先运行。
  • 然后,运行所有其他基于Django的测试(基于SimpleTestCase的测试用例,包括TransactionTestCase),而不保证或强制执行其中的特定顺序。
  • 然后运行任何其他可能更改数据库但不将其恢复到原始状态的unittest.TestCase测试(包括doctests)。

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

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

回滚仿真

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

Django can reload that data for you on a per-testcase basis by setting the serialized_rollback option to True in the body of the TestCase or TransactionTestCase, but note that this will slow down that test suite by approximately 3x.

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

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

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

其他测试条件

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

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

了解测试输出

当您运行测试时,您会看到一些消息,因为测试运行器准备自己。 您可以使用命令行上的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选项在测试运行之间保留测试数据库。 它跳过创建和销毁动作,可以大大减少运行测试的时间。