编写和运行测试 — Django 文档
编写和运行测试
本文档分为两个主要部分。 首先,我们解释如何使用 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"')
当您 运行您的测试 时,测试实用程序的默认行为是在名称以 [ 开头的任何文件中查找所有测试用例(即 unittest.TestCase
的子类) X179X],从这些测试用例中自动构建一个测试套件,并运行该套件。
有关 unittest
的更多详细信息,请参阅 Python 文档。
测试应该在哪里进行?
默认的 :djadmin:`startapp` 模板会在新应用程序中创建一个 tests.py
文件。 如果您只有几个测试,这可能没问题,但是随着测试套件的增长,您可能希望将其重组为测试包,以便您可以将测试拆分为不同的子模块,例如 test_models.py
、test_views.py
、test_forms.py
等。 随意选择您喜欢的任何组织方案。
另请参阅 使用 Django 测试运行器测试可重用应用程序 。
警告
如果您的测试依赖于数据库访问,例如创建或查询模型,请确保将您的测试类创建为 django.test.TestCase 而不是 unittest.TestCase
的子类。
使用 unittest.TestCase
避免了在事务中运行每个测试和刷新数据库的成本,但是如果您的测试与数据库交互,它们的行为将根据测试运行器执行它们的顺序而有所不同。 这可能导致单元测试在单独运行时通过但在套件中运行时失败。
运行测试
编写测试后,使用项目 manage.py
实用程序的 :djadmin:`test` 命令运行它们:
$ ./manage.py test
测试发现基于 unittest 模块的 内置测试发现 。 默认情况下,这将在当前工作目录下任何名为“test*.py”的文件中发现测试。
您可以通过向 ./manage.py test
提供任意数量的“测试标签”来指定要运行的特定测试。 每个测试标签可以是包、模块、TestCase
子类或测试方法的完整 Python 虚线路径。 例如:
# 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/
如果您的测试文件的名称与 test*.py
模式不同,您可以使用 -p
(或 --pattern
)选项指定自定义文件名模式匹配:
$ ./manage.py test --pattern="tests_*.py"
如果在测试运行时按 Ctrl-C
,测试运行器将等待当前运行的测试完成,然后正常退出。 在正常退出期间,测试运行器将输出任何测试失败的详细信息,报告运行了多少测试以及遇到了多少错误和失败,并像往常一样销毁任何测试数据库。 因此,如果您忘记通过 --failfast
选项,按下 Ctrl-C
会非常有用,请注意某些测试意外失败并希望获取有关失败的详细信息,而无需等待完整的测试运行完成.
如果您不想等待当前正在运行的测试完成,您可以再次按 Ctrl-C
,测试运行将立即停止,但不会正常运行。 不会报告中断前运行的测试的详细信息,并且不会破坏运行创建的任何测试数据库。
在启用警告的情况下进行测试
在启用 Python 警告的情况下运行测试是个好主意:python -Wa manage.py test
。 -Wa
标志告诉 Python 显示弃用警告。 与许多其他 Python 库一样,Django 使用这些警告来标记功能何时消失。 它还可能会标记您的代码中并非严格错误但可以从更好的实现中受益的区域。
测试数据库
需要数据库的测试(即模型测试)不会使用您的“真实”(生产)数据库。 为测试创建单独的空白数据库。
无论测试通过还是失败,当所有测试都执行完毕后,测试数据库就会被销毁。
您可以使用 test --keepdb
选项防止测试数据库被破坏。 这将在运行之间保留测试数据库。 如果数据库不存在,将首先创建它。 还将应用任何迁移以使其保持最新状态。
如上一节所述,如果测试运行被强行中断,测试数据库可能不会被破坏。 下次运行时,系统会询问您是要重用还是销毁数据库。 使用 test --noinput
选项取消该提示并自动销毁数据库。 这在持续集成服务器上运行测试时很有用,例如,测试可能会因超时而中断。
默认测试数据库名称是通过将 test_
添加到 :setting:`DATABASES` 中每个 :setting:`NAME` 的值来创建的。 使用 SQLite 时,测试将默认使用内存数据库(即,数据库将在内存中创建,完全绕过文件系统!)。 这 :设置:`测试 ` 字典在 :设置:`数据库` 提供了许多设置来配置您的测试数据库。 例如,如果要使用不同的数据库名称,请指定 :setting:`名称 ` 在里面 :设置:`测试 ` 任何给定数据库的字典 :设置:`数据库` .
在 PostgreSQL 上,:setting:`USER` 还需要对内置 postgres
数据库的读取权限。
除了使用单独的数据库之外,测试运行器还将使用您在设置文件中的所有相同数据库设置: :设置:`引擎 ` , :设置:`用户` , :设置:`主机` , 等等。 测试数据库由 :setting:`USER` 指定的用户创建,因此您需要确保给定的用户帐户具有足够的权限在系统上创建新数据库。
要对测试数据库的字符编码进行细粒度控制,请使用 :设置:`字符集 ` 测试选项。 如果您使用的是 MySQL,您还可以使用 :设置:`整理 ` 用于控制测试数据库使用的特定排序规则的选项。 有关这些和其他高级设置的详细信息,请参阅 设置文档 。
如果在 SQLite 中使用 SQLite 内存数据库,则启用 共享缓存 ,因此您可以编写具有在线程之间共享数据库的能力的测试。
在运行测试时从生产数据库中查找数据?
如果您的代码在编译模块时尝试访问数据库,这将发生 before 测试数据库设置,可能会出现意外结果。 例如,如果您在模块级代码中有一个数据库查询并且存在一个真实的数据库,那么生产数据可能会污染您的测试。 无论如何在您的代码中使用此类导入时数据库查询是一个坏主意 - 重写您的代码,使其不会这样做。
这也适用于 ready() 的自定义实现。
执行测试的顺序
为了保证所有 TestCase
代码都以干净的数据库开头,Django 测试运行程序按以下方式重新排序测试:
- 首先运行所有 TestCase 子类。
- 然后,所有其他基于 Django 的测试(基于 SimpleTestCase 的测试用例,包括 TransactionTestCase)在没有特定顺序保证或强制执行的情况下运行。
- 然后运行任何其他
unittest.TestCase
测试(包括 doctests),这些测试可能会改变数据库而不将其恢复到其原始状态。
您可以使用 test --reverse
选项反转组内的执行顺序。 这有助于确保您的测试彼此独立。
回滚仿真
迁移中加载的任何初始数据仅在 TestCase
测试中可用,而在 TransactionTestCase
测试中不可用,而且仅在支持事务的后端上可用(最重要的例外是 MyISAM)。 对于依赖 TransactionTestCase
的测试也是如此,例如 LiveServerTestCase 和 StaticLiveServerTestCase。
Django 可以通过在 TestCase
或 TransactionTestCase
的主体中将 serialized_rollback
选项设置为 True
来为每个测试用例重新加载该数据,但是请注意,这会使该测试套件的速度降低大约 3 倍。
第三方应用程序或针对 MyISAM 开发的应用程序需要设置此项; 但是,一般而言,您应该针对事务数据库开发自己的项目,并在大多数测试中使用 TestCase
,因此不需要此设置。
初始序列化通常非常快,但是如果您希望从该过程中排除某些应用程序(并稍微加快测试运行速度),您可以将这些应用程序添加到 :setting:`TEST_NON_SERIALIZED_APPS`。
为了防止序列化数据被加载两次,设置 serialized_rollback=True
在刷新测试数据库时禁用 post_migrate 信号。
其他测试条件
无论配置文件中 :setting:`DEBUG` 设置的值如何,所有 Django 测试都以 :setting:`DEBUG`=False 运行。 这是为了确保观察到的代码输出与在生产环境中看到的输出相匹配。
每次测试后都不会清除缓存,如果在生产中运行测试,运行“manage.py test fooapp”可以将测试中的数据插入实时系统的缓存中,因为与数据库不同,单独的“测试缓存”不是用过的。 这种行为 :ticket:`可能会改变 <11505>` 在将来。
了解测试输出
当您运行测试时,您会在测试运行器自行准备时看到许多消息。 您可以使用命令行上的 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
库的文档。
请注意,对于任意数量的失败和错误测试,test-runner 脚本的返回码为 1。 如果所有测试都通过,则返回码为 0。 如果您在 shell 脚本中使用 test-runner 脚本并且需要在该级别测试成功或失败,则此功能非常有用。
加速测试
并行运行测试
只要您的测试被正确隔离,您就可以并行运行它们以提高多核硬件的速度。 参见 test --parallel
。
密码散列
默认密码散列器的设计相当慢。 如果您在测试中对许多用户进行身份验证,您可能需要使用自定义设置文件并将 :setting:`PASSWORD_HASHERS` 设置设置为更快的散列算法:
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.MD5PasswordHasher',
]
不要忘记在 :setting:`PASSWORD_HASHERS` 中包含任何用于装置的散列算法,如果有的话。
保留测试数据库
test --keepdb
选项在测试运行之间保留测试数据库。 它跳过创建和销毁操作,这可以大大减少运行测试的时间。