我爱编程

测试工具

2018-02-25  本文已影响49人  大爷的二舅

Django提供了一组工具,在编写测试时可以派上用场。

测试客户端

测试客户端是一个充当虚拟Web浏览器的Python类,允许您以编程方式测试视图并与Django驱动的应用程序进行交互。 测试客户端可以做的一些事情是:

请注意,测试客户端不是为了替代Selenium或其他浏览器内的框架。 Django的测试客户端有不同的重点。

简而言之:

Django还为这些框架提供特别支持; 有关更多详细信息,请参阅LiveServerTestCase部分。 综合测试套件应该使用两种测试类型的组合。 有关示例的更详细的Django测试客户端,请参阅Django Project网站。

提供了测试用例类

正常的Python单元测试类扩展了unittest.TestCase的基类。 Django提供了这个基类的一些扩展:

SimpleTestCase

用一些基本功能扩展unittest.TestCase,如:

TransactionTestCase

Django的TestCase类(如下所述)利用数据库事务工具来加速在每次测试开始时将数据库重置为已知状态的过程。 但是,这样做的后果是某些数据库行为无法在Django TestCase类中进行测试。

在这些情况下,您应该使用TransactionTestCase。 除了数据库重置为已知状态的方式以及测试代码测试提交和回滚效果的能力之外,TransactionTestCase和TestCase是相同的:

TransactionTestCase继承自SimpleTestCase。

TestCase

此类提供了一些可用于测试网站的附加功能。 将正常的unittest.TestCase转换为Django TestCase很简单:只需将您的测试的基类从unittest.TestCase更改为django.test.TestCase。 所有标准的Python单元测试功能都将继续可用,但它会增加一些有用的附加功能,其中包括:

TestCase继承自TransactionTestCase。

LiveServerTestCase

LiveServerTestCase与TransactionTestCase的基本相同,只有一个额外的功能:它在安装时在后台启动一个活的Django服务器,并在卸载时关闭它。 这允许使用除Django虚拟客户端以外的自动化测试客户端,例如Selenium客户端,
在浏览器内执行一系列功能测试并模拟真实用户的操作。

测试用例功能
默认测试客户端

*TestCase实例中的每个测试用例都可以访问Django测试客户端的实例。 该客户端可以作为self.client访问。 这个客户端是为每个测试重新创建的,所以您不必担心状态(如cookie)从一个测试传递到另一个测试。 这意味着,而不是在每个测试中实例化一个客户端:

import unittest
from django.test import Client

class SimpleTest(unittest.TestCase):
    def test_details(self):
        client = Client()
        response = client.get('/customer/details/')
        self.assertEqual(response.status_code, 200)

    def test_index(self):
        client = Client()
        response = client.get('/customer/index/')
        self.assertEqual(response.status_code, 200)

...您可以参考self.client,如下所示:

from django.test import TestCase

class SimpleTest(TestCase):
    def test_details(self):
        response = self.client.get('/customer/details/')
        self.assertEqual(response.status_code, 200)

    def test_index(self):
        response = self.client.get('/customer/index/')
        self.assertEqual(response.status_code, 200)
Fixture装载

如果数据库中没有任何数据,则用于数据库支持的Web站点的测试用例用处不大。 为了使测试数据容易放入数据库,Django的自定义TransactionTestCase类提供了一种加载Fixture的方法。 fixture是Django知道如何导入数据库的数据集合。 例如,如果您的网站具有用户帐户,则可能会设置一个假用户帐户的夹具,以便在测试期间填充数据库。

创建fixture的最直接的方法是使用manage.py dumpdata命令。 这假定你已经在你的数据库中有一些数据。 (有关更多详细信息,请参阅dumpdata文档)。

一旦你创建了一个fixture并把它放在你的INSTALLED_APPS的fixtures目录中,你就可以在你的单元测试中通过在你的django.test.TestCase子类中指定一个fixtures class属性来使用它:

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

class AnimalTestCase(TestCase):
    fixtures = ['mammals.json', 'birds']

    def setUp(self):
        # Test definitions as before.
        call_setup_methods()

    def testFluffyAnimals(self):
        # A test that uses the fixtures.
        call_some_test_code()

具体会发生什么:

有关定义和安装夹具的更多详细信息,请参阅loaddata文档。
对测试用例中的每个测试都重复此刷新/加载过程,因此您可以确定测试的结果不会受到另一个测试或测试执行顺序的影响。 默认情况下,灯具只加载到默认数据库中。 如果您正在使用多个数据库并将multi_db设置为True,则会将Fixture加载到所有数据库中。

覆写设置

使用以下功能临时更改测试中的设置值。 不要直接操作django.conf.settings,因为在这种操作之后,Django不会恢复原始值。

settings()

出于测试目的,在运行测试代码后暂时更改设置并恢复为原始值通常很有用。 对于这个用例,Django提供了一个名为settings()的标准Python上下文管理器(请参阅PEP 343),它可以像这样使用:

from django.test import TestCase

class LoginTestCase(TestCase):

    def test_login(self):

        # First check for the default behavior
        response = self.client.get('/sekrit/')
        self.assertRedirects(response, '/accounts/login/?next=/sekrit/')

        # Then override the LOGIN_URL setting
        with self.settings(LOGIN_URL='/other/login/'):
            response = self.client.get('/sekrit/')
            self.assertRedirects(response, '/other/login/?next=/sekrit/')

此示例将覆盖with块中代码的LOGIN_URL设置,然后将其值重置为之前的状态。

modify_settings()

重新定义包含值列表的设置可能难以实现。 实际上,增加或删除值通常就足够了。 modify_settings()上下文管理器使其变得非常简单:

from django.test import TestCase

class MiddlewareTestCase(TestCase):

    def test_cache_middleware(self):
        with self.modify_settings(MIDDLEWARE_CLASSES={
            'append': 'django.middleware.cache.FetchFromCacheMiddleware',
            'prepend': 'django.middleware.cache.UpdateCacheMiddleware',
            'remove': [
               'django.contrib.sessions.middleware.SessionMiddleware',
               'django.contrib.auth.middleware.AuthenticationMiddleware', 
               'django.contrib.messages.middleware.MessageMiddleware',
            ],
        }):
            response = self.client.get('/')
            # ...

对于每个操作,您可以提供值列表或字符串。 当该值已经存在于列表中时,append和prepend不起作用; 当值不存在时,也不会删除。

override_settings()

如果你想覆盖一个测试方法的设置,Django提供了override_settings()装饰器(见PEP 318)。 它是这样使用的:

from django.test import TestCase, override_settings

class LoginTestCase(TestCase):

    @override_settings(LOGIN_URL='/other/login/')
    def test_login(self):
        response = self.client.get('/sekrit/')
        self.assertRedirects(response, '/other/login/?next=/sekrit/')

装饰器也可以应用于TestCase类:

from django.test import TestCase, override_settings

@override_settings(LOGIN_URL='/other/login/')
class LoginTestCase(TestCase):

    def test_login(self):
        response = self.client.get('/sekrit/')
        self.assertRedirects(response, '/other/login/?next=/sekrit/')
modify_settings()

同样,Django提供了modify_settings()装饰器:

from django.test import TestCase, modify_settings

class MiddlewareTestCase(TestCase):

    @modify_settings(MIDDLEWARE_CLASSES={
        'append': 'django.middleware.cache.FetchFromCacheMiddleware',
        'prepend': 'django.middleware.cache.UpdateCacheMiddleware',
    })
    def test_cache_middleware(self):
        response = self.client.get('/')
        # ...

装饰器也可以应用于测试用例类:

from django.test import TestCase, modify_settings

@modify_settings(MIDDLEWARE_CLASSES={
    'append': 'django.middleware.cache.FetchFromCacheMiddleware',
    'prepend': 'django.middleware.cache.UpdateCacheMiddleware',
})
class MiddlewareTestCase(TestCase):

    def test_cache_middleware(self):
        response = self.client.get('/')
        # ...

在覆盖设置时,请确保处理应用代码使用缓存或保留状态的类似功能的情况,即使设置已更改。 Django提供django.test.signals.setting_changed信号,允许您注册回调以清除并在设置更改时重置状态。

断言

由于Python的普通unittest.TestCase类实现了assertTrue()和assertEqual()等断言方法,因此Django的自定义TestCase类提供了许多可用于测试Web应用程序的自定义断言方法:

电邮服务

如果您的任何Django视图使用Django的电子邮件功能发送电子邮件,那么您可能不希望每次使用该视图运行测试时发送电子邮件。出于这个原因,Django的测试运行器会自动将所有Django发送的电子邮件重定向到一个虚拟发件箱。这使您可以测试发送电子邮件的每个方面 - 从发送到每封邮件内容的邮件数量 - 而不实际发送邮件。测试运行者通过用测试后端透明地替换正常的电子邮件后端来完成此操作。 (别担心 - 如果您正在运行其他电子邮件发件人,则不会影响Django以外的任何其他电子邮件发件人,例如您的计算机的邮件服务器。)

在测试运行期间,每封发出的电子邮件都保存在django.core.mail.outbox中。这是已发送的所有EmailMessage实例的简单列表。 outbox属性是仅在使用locmem电子邮件后端时创建的特殊属性。它通常不会作为django.core.mail模块的一部分存在,您无法直接导入它。下面的代码显示了如何正确访问该属性。以下是一个示例测试,用于检查django.core.mail.outbox的长度和内容:

from django.core import mail
from django.test import TestCase

class EmailTest(TestCase):
    def test_send_email(self):
        # Send message.
        mail.send_mail('Subject here', 'Here is the message.',
            'from@example.com', ['to@example.com'],
            fail_silently=False)

        # Test that one message has been sent.
        self.assertEqual(len(mail.outbox), 1)

        # Verify that the subject of the first message is correct.
        self.assertEqual(mail.outbox[0].subject, 'Subject here')

如前所述,在Django * TestCase的每个测试开始时,测试发件箱都将被清空。 要手动清空发件箱,请将空列表分配给mail.outbox:

from django.core import mail

# Empty the test outbox
mail.outbox = []
管理命令

可以使用call_command()函数来测试管理命令。 输出可以被重定向到一个StringIO实例中:

from django.core.management import call_command
from django.test import TestCase
from django.utils.six import StringIO

class ClosepollTest(TestCase):
    def test_command_output(self):
        out = StringIO()
        call_command('closepoll', stdout=out)
        self.assertIn('Expected output', out.getvalue())
跳过测试

unittest库提供了@skipIf和@skipUnless修饰器,如果您事先知道这些测试在某些条件下会失败,您可以跳过测试。 例如,如果您的测试需要特定的可选库才能成功,则可以使用@skipIf修饰测试用例。 然后,测试运行人员将报告测试未执行以及为什么,而不是通过测试或完全省略测试。

测试数据库

需要数据库的测试(即模型测试)不会使用您的生产数据库; 为测试创建单独的空白数据库。 无论测试通过还是失败,测试数据库在所有测试执行完毕后都会被销毁。 您可以通过向测试命令添加--keepdb标志来防止销毁测试数据库。 这将在运行之间保留测试数据库。

如果数据库不存在,它将首先被创建。 任何迁移也将被应用,以保持它的最新状态。 默认情况下,测试数据库通过将test_添加到NAME的值来获取其名称
DATABASES中定义的数据库的设置。 在使用SQLite数据库引擎时,测试默认使用内存数据库(即,数据库将在内存中创建,完全绕过文件系统!)。

如果要使用不同的数据库名称,请在TEST字典中为DATABASES中的任何给定数据库指定NAME。 在PostgreSQL上,USER还需要对内置postgres数据库的读取权限。 除了使用单独的数据库以外,测试运行器还会使用您的设置文件中所有相同的数据库设置:ENGINE,USER,HOST等。测试数据库由USER指定的用户创建,因此您可以 需要确保给定的用户帐户有足够的权限在系统上创建新的数据库。

使用不同的测试框架

显然,unittest不是唯一的Python测试框架。 虽然Django没有提供对替代框架的明确支持,但它确实提供了一种方法来调用为替代框架构建的测试,就好像它们是正常的Django测试一样。

当你运行 ./manage.py test时,Django会查看TEST_RUNNER设置以确定要执行的操作。 默认情况下,TEST_RUNNER指向django.test.runner.DiscoverRunner。 这个类定义了默认的Django测试行为。 这种行为涉及:

  1. 执行全局预测试设置。

  2. 在名称与模式test * .py匹配的当前目录下面的任何文件中查找测试。

  3. 创建测试数据库。

  4. 正在运行迁移以将模型和初始数据安装到测试数据库中。

  5. 运行找到的测试。

  6. 销毁测试数据库。

  7. 执行全局的测试后拆解。

如果您定义了您自己的测试运行器类并在该类上指向了TEST_RUNNER,则只要运行 ./manage.py test,Django就会执行测试运行器。

通过这种方式,可以使用任何可以从Python代码执行的测试框架,或者修改Django测试执行流程以满足您可能具有的任何测试需求。

有关使用不同测试框架的更多信息,请参阅Django项目网站。

下一步是什么?

既然您已经知道如何为您的Django项目编写测试,那么一旦您准备将项目变成一个真正的实时网站 - 我们将把Django部署到一个Web服务器上,我们将继续讨论一个非常重要的主题。

上一篇下一篇

猜你喜欢

热点阅读