编写和运行测试

本文档分为两个主要部分。 首先,我们解释如何用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. Feel free to pick whatever organizational scheme you like.

另请参阅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 test提供任意数量的“测试标签”来指定要运行的特定测试。 Each test label can be a full Python dotted path to a package, module, TestCase subclass, or test method. 例如:

# 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/

You can specify a custom filename pattern match using the -p (or --pattern) option, if your test files are named differently from the test*.py pattern:

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

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

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

测试并启用警告

启用Python警告运行测试是一个好主意:python -Wall manage.py test > T0>。 -Wall标志告诉Python显示弃用警告。 与许多其他Python库一样,Django使用这些警告来标记功能何时消失。 它也可能会在代码中标记并非严格错误但可以从更好的实现中受益的区域。

测试数据库

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

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

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

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

On PostgreSQL, USER will also need read access to the built-in postgres database.

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

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

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

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

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

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

执行测试的顺序

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

  • 所有的TestCase子类首先运行。
  • Then, all other Django-based tests (test cases based on SimpleTestCase, including TransactionTestCase) are run with no particular ordering guaranteed nor enforced among them.
  • Then any other unittest.TestCase tests (including doctests) that may alter the database without restoring it to its original state are run.

注意

新的测试顺序可能会揭示测试用例排序的意外依赖性。 This is the case with doctests that relied on state left in the database by a given TransactionTestCase test, they must be updated to be able to run independently.

您可以使用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选项在测试运行之间保留测试数据库。 它跳过创建和销毁操作,这可以大大减少运行测试的时间。