Django4 中文入门教程 Django4.0 测试工具-提供的测试用例类

2024-02-25 开发教程 Django4 中文入门教程 匿名 4

一般的 Python 单元测试类都会扩展一个基类 ​unittest.TestCase​。Django 提供了这个基类的一些扩展。

Django 单元测试类的层次结构
你可以将一个普通的 ​unittest.TestCase​ 转换为任何一个子类:将你的测试基类从 ​unittest.TestCase​ 改为子类。所有标准的 Python 单元测试功能都将是可用的,并且它将被一些有用的附加功能所增强,如下面每节所述。

SimpleTestCase

class SimpleTestCase​

unittest.TestCase​ 的一个子类,增加了以下功能:

一些有用的断言,例如:

  • 检查一个可调用对象 会引发某个异常。
  • 检查一个可调用对象 会触发某个警告。
  • 测试表单字段 渲染和错误处理。
  • 测试 HTML 响应是否存在/缺乏给定的片段。
  • 验证模板 是否被用于生成给定的响应内容。
  • 验证两个 URL 是否相等。
  • 验证 HTTP 重定向是由应用程序执行的。
  • 严格测试两个 HTML 片段 是否相等或 包含。
  • 严格测试两个 XML 片段 是否相等。
  • 严格测试两个 JSON 片段 相等。
  • 能够用 修改后的配置 运行测试。
  • 使用 client Client。

如果你的测试进行任何数据库查询,请使用子类 ​TransactionTestCase ​或 ​TestCase​。

SimpleTestCase.databases

SimpleTestCase ​默认不允许数据库查询。这有助于避免执行写查询而影响其他测试,因为每个 ​SimpleTestCase ​测试不是在事务中运行的。如果你不关心这个问题,你可以通过在你的测试类上设置 ​databases ​类属性为 ​__all__​来禁止这个行为。

SimpleTestCase ​和它的子类(如 ​TestCase​)依靠 ​setUpClass()​ 和 ​tearDownClass()​ 来执行一些全类范围的初始化(如覆盖配置)。如果你需要覆盖这些方法,别忘了调用 ​super ​实现:

class MyTestCase(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
...
@classmethod
def tearDownClass(cls):
...
super().tearDownClass()

如果在 ​setUpClass()​ 过程中出现异常,一定要考虑到 Python 的行为。如果发生这种情况,类中的测试和 ​tearDownClass()​ 都不会被运行。在 ​django.test.TestCase​ 的情况下,这将会泄露在 ​super()​ 中创建的事务,从而导致各种症状,包括在某些平台上的分段故障(在 macOS 上报告)。如果你想在 ​setUpClass()​ 中故意引发一个异常,如 ​unittest.SkipTest​,一定要在调用 ​super()​ 之前进行,以避免这种情况。

TransactionTestCase

class TransactionTestCase

TransactionTestCase ​继承自 ​SimpleTestCase ​以增加一些数据库特有的功能:

  • 在每次测试开始时将数据库重新设置为已知状态,以方便测试和使用 ORM。
  • 数据库 ​fixtures
  • 测试 基于数据库后端功能的跳过.
  • 其他专门的​assert*​ 方法。

Django 的 ​TestCase ​类是 ​TransactionTestCase ​的一个比较常用的子类,它利用数据库事务设施来加快在每次测试开始时将数据库重置到已知状态的过程。然而,这样做的一个后果是,有些数据库行为不能在 Django ​TestCase​ 类中进行测试。例如,你不能像使用 ​select_for_update()​ 时那样,测试一个代码块是否在一个事务中执行。在这些情况下,你应该使用 ​TransactionTestCase​。
TransactionTestCase ​和 ​TestCase ​除了将数据库重设为已知状态的方式和测试与测试提交和回滚效果的相关代码外,其他都是相同的。

  • TransactionTestCase ​在测试运行后,通过清空所有表来重置数据库。​TransactionTestCase ​可以调用提交和回滚,并观察这些调用对数据库的影响。
  • 另一方面,​TestCase ​在测试后不清空表。相反,它将测试代码包含在数据库事务中,在测试结束后回滚。这保证了测试结束时的回滚能将数据库恢复到初始状态。

在不支持回滚的数据库上运行的 ​TestCase ​(例如 MyISAM 存储引擎的 MySQL ),则 ​TransactionTestCase ​的所有实例,将在测试结束时回滚,删除测试数据库中的所有数据。
应用 不会看到他们的数据被重新加载;如果你需要这个功能(例如,第三方应用应该启用这个功能),你可以在 ​TestCase ​中设置 ​serialized_rollback = True​。

TestCase

class TestCase

这是 Django 中最常用的编写测试的类。它继承自 ​TransactionTestCase ​(以及扩展自 ​SimpleTestCase​)。如果你的 Django 应用程序不使用数据库,就使用 ​SimpleTestCase​。

  • 在两个嵌套的 ​atomic()​ 块中封装测试:一个用于整个类,一个用于每个测试。因此,如果你想测试一些特定的数据库事务行为,可以使用 ​TransactionTestCase
  • 在每次测试结束时检查可延迟的数据库约束。

它还提供了另一种方法:

classmethod TestCase.setUpTestData()

上文所述的类级 ​atomic ​块允许在类级创建初始数据,整个 ​TestCase ​只需一次。与使用 ​setUp()​ 相比,这种技术允许更快的测试。
例如:

from django.test import TestCase
class MyTests(TestCase):
@classmethod
def setUpTestData(cls):
# Set up data for the whole TestCase
cls.foo = Foo.objects.create(bar="Test")
...
def test1(self):
# Some test using self.foo
...
def test2(self):
# Some other test using self.foo
...

请注意,如果测试是在没有事务支持的数据库上运行(例如,MyISAM 引擎的 MySQL),​setUpTestData()​ 将在每次测试前被调用,从而降低了速度优势。

classmethod TestCase.captureOnCommitCallbacks(using=DEFAULT_DB_ALIAS, execute=False)

返回一个为给定的数据库连接捕获 ​transaction.on_commit()​ 回调的上下文管理器。它返回一个列表,其中包含在退出上下文时,捕获的回调函数。从这个列表中,你可以对回调进行断言,或者调用它们来获得其副作用,模拟一个提交。
using ​是数据库连接的别名,用于捕获回调。
如果 ​execute ​是 ​True​,并且如果没有发生异常,所有的回调将在上下文管理器退出时被调用。这模拟了在包裹的代码块之后的提交。
例如:

from django.core import mail
from django.test import TestCase
class ContactTests(TestCase):
def test_post(self):
with self.captureOnCommitCallbacks(execute=True) as callbacks:
response = self.client.post(
'/contact/',
{'message': 'I like your site'},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(callbacks), 1)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, 'Contact Form')
self.assertEqual(mail.outbox[0].body, 'I like your site')

LiveServerTestCase

class LiveServerTestCase

LiveServerTestCase ​和 ​TransactionTestCase ​的功能基本相同,但多了一个功能:它在设置时在后台启动一个实时的 Django 服务器,并在关闭时将其关闭。这就允许使用 Django 虚拟客户端 以外的自动化测试客户端,例如,Selenium 客户端,在浏览器内执行一系列功能测试,并模拟真实用户的操作。

实时服务器在 localhost 上监听,并绑定到 0 号端口,0 号端口使用操作系统分配的一个空闲端口。在测试过程中可以用 ​self.live_server_url​ 访问服务器的 URL。
为了演示如何使用 ​LiveServerTestCase​,让我们写一个 Selenium 测试。首先,你需要将 ​selenium package​ 安装到你的 Python 路径中。

...\> py -m pip install selenium

然后,在你的应用程序的测试模块中添加一个基于 ​LiveServerTestCase ​的测试(例如:​myapp/tests.py​)。在这个例子中,我们将假设你正在使用 ​staticfiles ​应用,并且希望在执行测试时提供类似于我们在开发时使用 ​DEBUG=True​ 得到的静态文件,即不必使用 ​collectstatic ​收集它们。我们将使用 ​StaticLiveServerTestCase ​子类,它提供了这个功能。如果不需要的话,可以用 ​django.test.LiveServerTestCase​ 代替。
这个测试的代码可能如下:

from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium.webdriver.firefox.webdriver import WebDriver
class MySeleniumTests(StaticLiveServerTestCase):
fixtures = ['user-data.json']
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.selenium = WebDriver()
cls.selenium.implicitly_wait(10)
@classmethod
def tearDownClass(cls):
cls.selenium.quit()
super().tearDownClass()
def test_login(self):
self.selenium.get('%s%s' % (self.live_server_url, '/login/'))
username_input = self.selenium.find_element_by_name("username")
username_input.send_keys('myuser')
password_input = self.selenium.find_element_by_name("password")
password_input.send_keys('secret')
self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()

最后,你可以按以下方式进行测试:

...\> manage.py test myapp.tests.MySeleniumTests.test_login

这个例子会自动打开 Firefox,然后进入登录页面,输入凭证并按“登录”按钮。Selenium 提供了其他驱动程序,以防你没有安装 Firefox 或希望使用其他浏览器。

注解

当使用内存 SQLite 数据库运行测试时,同一个数据库连接将由两个线程并行共享:运行实时服务器的线程和运行测试用例的线程。要防止两个线程通过这个共享连接同时进行数据库查询,因为这有时可能会随机导致测试失败。所以你需要确保两个线程不会同时访问数据库。特别是,这意味着在某些情况下(例如,刚刚点击一个链接或提交一个表单之后),你可能需要检查 Selenium 是否收到了响应,并且在继续执行进一步的测试之前,检查下一个页面是否被加载。例如,让 Selenium 等待直到在响应中找到 ​<body>​ HTML 标签(需要 Selenium > 2.13):

def test_login(self):
from selenium.webdriver.support.wait import WebDriverWait
timeout = 2
...
self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()
# Wait until the response is received
WebDriverWait(self.selenium, timeout).until(
lambda driver: driver.find_element_by_tag_name('body'))

这里的棘手之处在于,实际上并没有页面加载之类的东西,尤其是在服务器生成初始文档后动态生成 HTML 的现代 Web 应用程序中。 因此,检查响应中是否存在 ​<body>​ 可能不一定适用于所有用例。