Django4.0 測試工具-提供的測試用例類

2022-03-29 16:18 更新

一般的 Python 單元測試類都會擴(kuò)展一個(gè)基類 ?unittest.TestCase?。Django 提供了這個(gè)基類的一些擴(kuò)展。

QQ截圖20220329161835

Django 單元測試類的層次結(jié)構(gòu)
你可以將一個(gè)普通的 ?unittest.TestCase? 轉(zhuǎn)換為任何一個(gè)子類:將你的測試基類從 ?unittest.TestCase? 改為子類。所有標(biāo)準(zhǔn)的 Python 單元測試功能都將是可用的,并且它將被一些有用的附加功能所增強(qiáng),如下面每節(jié)所述。

SimpleTestCase

class SimpleTestCase?

unittest.TestCase? 的一個(gè)子類,增加了以下功能:

一些有用的斷言,例如:

  • 檢查一個(gè)可調(diào)用對象 會引發(fā)某個(gè)異常。
  • 檢查一個(gè)可調(diào)用對象 會觸發(fā)某個(gè)警告。
  • 測試表單字段 渲染和錯(cuò)誤處理。
  • 測試 HTML 響應(yīng)是否存在/缺乏給定的片段。
  • 驗(yàn)證模板 是否被用于生成給定的響應(yīng)內(nèi)容。
  • 驗(yàn)證兩個(gè) URL 是否相等。
  • 驗(yàn)證 HTTP 重定向是由應(yīng)用程序執(zhí)行的。
  • 嚴(yán)格測試兩個(gè) HTML 片段 是否相等或 包含。
  • 嚴(yán)格測試兩個(gè) XML 片段 是否相等。
  • 嚴(yán)格測試兩個(gè) JSON 片段 相等。
  • 能夠用 修改后的配置 運(yùn)行測試。
  • 使用 client Client。

如果你的測試進(jìn)行任何數(shù)據(jù)庫查詢,請使用子類 ?TransactionTestCase ?或 ?TestCase?。

SimpleTestCase.databases

?SimpleTestCase ?默認(rèn)不允許數(shù)據(jù)庫查詢。這有助于避免執(zhí)行寫查詢而影響其他測試,因?yàn)槊總€(gè) ?SimpleTestCase ?測試不是在事務(wù)中運(yùn)行的。如果你不關(guān)心這個(gè)問題,你可以通過在你的測試類上設(shè)置 ?databases ?類屬性為 ?__all__?來禁止這個(gè)行為。

?SimpleTestCase ?和它的子類(如 ?TestCase?)依靠 ?setUpClass()? 和 ?tearDownClass()? 來執(zhí)行一些全類范圍的初始化(如覆蓋配置)。如果你需要覆蓋這些方法,別忘了調(diào)用 ?super ?實(shí)現(xiàn):

class MyTestCase(TestCase):

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        ...

    @classmethod
    def tearDownClass(cls):
        ...
        super().tearDownClass()

如果在 ?setUpClass()? 過程中出現(xiàn)異常,一定要考慮到 Python 的行為。如果發(fā)生這種情況,類中的測試和 ?tearDownClass()? 都不會被運(yùn)行。在 ?django.test.TestCase? 的情況下,這將會泄露在 ?super()? 中創(chuàng)建的事務(wù),從而導(dǎo)致各種癥狀,包括在某些平臺上的分段故障(在 macOS 上報(bào)告)。如果你想在 ?setUpClass()? 中故意引發(fā)一個(gè)異常,如 ?unittest.SkipTest?,一定要在調(diào)用 ?super()? 之前進(jìn)行,以避免這種情況。

TransactionTestCase

class TransactionTestCase

?TransactionTestCase ?繼承自 ?SimpleTestCase ?以增加一些數(shù)據(jù)庫特有的功能:

  • 在每次測試開始時(shí)將數(shù)據(jù)庫重新設(shè)置為已知狀態(tài),以方便測試和使用 ORM。
  • 數(shù)據(jù)庫 ?fixtures?
  • 測試 基于數(shù)據(jù)庫后端功能的跳過.
  • 其他專門的? assert*? 方法。

Django 的 ?TestCase ?類是 ?TransactionTestCase ?的一個(gè)比較常用的子類,它利用數(shù)據(jù)庫事務(wù)設(shè)施來加快在每次測試開始時(shí)將數(shù)據(jù)庫重置到已知狀態(tài)的過程。然而,這樣做的一個(gè)后果是,有些數(shù)據(jù)庫行為不能在 Django ?TestCase? 類中進(jìn)行測試。例如,你不能像使用 ?select_for_update()? 時(shí)那樣,測試一個(gè)代碼塊是否在一個(gè)事務(wù)中執(zhí)行。在這些情況下,你應(yīng)該使用 ?TransactionTestCase?。
?TransactionTestCase ?和 ?TestCase ?除了將數(shù)據(jù)庫重設(shè)為已知狀態(tài)的方式和測試與測試提交和回滾效果的相關(guān)代碼外,其他都是相同的。

  • ?TransactionTestCase ?在測試運(yùn)行后,通過清空所有表來重置數(shù)據(jù)庫。?TransactionTestCase ?可以調(diào)用提交和回滾,并觀察這些調(diào)用對數(shù)據(jù)庫的影響。
  • 另一方面,?TestCase ?在測試后不清空表。相反,它將測試代碼包含在數(shù)據(jù)庫事務(wù)中,在測試結(jié)束后回滾。這保證了測試結(jié)束時(shí)的回滾能將數(shù)據(jù)庫恢復(fù)到初始狀態(tài)。

在不支持回滾的數(shù)據(jù)庫上運(yùn)行的 ?TestCase ?(例如 MyISAM 存儲引擎的 MySQL ),則 ?TransactionTestCase ?的所有實(shí)例,將在測試結(jié)束時(shí)回滾,刪除測試數(shù)據(jù)庫中的所有數(shù)據(jù)。
應(yīng)用 不會看到他們的數(shù)據(jù)被重新加載;如果你需要這個(gè)功能(例如,第三方應(yīng)用應(yīng)該啟用這個(gè)功能),你可以在 ?TestCase ?中設(shè)置 ?serialized_rollback = True?。

TestCase

class TestCase

這是 Django 中最常用的編寫測試的類。它繼承自 ?TransactionTestCase ?(以及擴(kuò)展自 ?SimpleTestCase?)。如果你的 Django 應(yīng)用程序不使用數(shù)據(jù)庫,就使用 ?SimpleTestCase?。

  • 在兩個(gè)嵌套的 ?atomic()? 塊中封裝測試:一個(gè)用于整個(gè)類,一個(gè)用于每個(gè)測試。因此,如果你想測試一些特定的數(shù)據(jù)庫事務(wù)行為,可以使用 ?TransactionTestCase?
  • 在每次測試結(jié)束時(shí)檢查可延遲的數(shù)據(jù)庫約束。

它還提供了另一種方法:

classmethod TestCase.setUpTestData()

上文所述的類級 ?atomic ?塊允許在類級創(chuàng)建初始數(shù)據(jù),整個(gè) ?TestCase ?只需一次。與使用 ?setUp()? 相比,這種技術(shù)允許更快的測試。
例如:

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
        ...

請注意,如果測試是在沒有事務(wù)支持的數(shù)據(jù)庫上運(yùn)行(例如,MyISAM 引擎的 MySQL),?setUpTestData()? 將在每次測試前被調(diào)用,從而降低了速度優(yōu)勢。

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

返回一個(gè)為給定的數(shù)據(jù)庫連接捕獲 ?transaction.on_commit()? 回調(diào)的上下文管理器。它返回一個(gè)列表,其中包含在退出上下文時(shí),捕獲的回調(diào)函數(shù)。從這個(gè)列表中,你可以對回調(diào)進(jìn)行斷言,或者調(diào)用它們來獲得其副作用,模擬一個(gè)提交。
?using ?是數(shù)據(jù)庫連接的別名,用于捕獲回調(diào)。
如果 ?execute ?是 ?True?,并且如果沒有發(fā)生異常,所有的回調(diào)將在上下文管理器退出時(shí)被調(diào)用。這模擬了在包裹的代碼塊之后的提交。
例如:

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 ?的功能基本相同,但多了一個(gè)功能:它在設(shè)置時(shí)在后臺啟動一個(gè)實(shí)時(shí)的 Django 服務(wù)器,并在關(guān)閉時(shí)將其關(guān)閉。這就允許使用 Django 虛擬客戶端 以外的自動化測試客戶端,例如,Selenium 客戶端,在瀏覽器內(nèi)執(zhí)行一系列功能測試,并模擬真實(shí)用戶的操作。

實(shí)時(shí)服務(wù)器在 localhost 上監(jiān)聽,并綁定到 0 號端口,0 號端口使用操作系統(tǒng)分配的一個(gè)空閑端口。在測試過程中可以用 ?self.live_server_url? 訪問服務(wù)器的 URL。
為了演示如何使用 ?LiveServerTestCase?,讓我們寫一個(gè) Selenium 測試。首先,你需要將 ?selenium package? 安裝到你的 Python 路徑中。

...\> py -m pip install selenium

然后,在你的應(yīng)用程序的測試模塊中添加一個(gè)基于 ?LiveServerTestCase ?的測試(例如:?myapp/tests.py?)。在這個(gè)例子中,我們將假設(shè)你正在使用 ?staticfiles ?應(yīng)用,并且希望在執(zhí)行測試時(shí)提供類似于我們在開發(fā)時(shí)使用 ?DEBUG=True? 得到的靜態(tài)文件,即不必使用 ?collectstatic ?收集它們。我們將使用 ?StaticLiveServerTestCase ?子類,它提供了這個(gè)功能。如果不需要的話,可以用 ?django.test.LiveServerTestCase? 代替。
這個(gè)測試的代碼可能如下:

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()

最后,你可以按以下方式進(jìn)行測試:

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

這個(gè)例子會自動打開 Firefox,然后進(jìn)入登錄頁面,輸入憑證并按“登錄”按鈕。Selenium 提供了其他驅(qū)動程序,以防你沒有安裝 Firefox 或希望使用其他瀏覽器。

注解

當(dāng)使用內(nèi)存 SQLite 數(shù)據(jù)庫運(yùn)行測試時(shí),同一個(gè)數(shù)據(jù)庫連接將由兩個(gè)線程并行共享:運(yùn)行實(shí)時(shí)服務(wù)器的線程和運(yùn)行測試用例的線程。要防止兩個(gè)線程通過這個(gè)共享連接同時(shí)進(jìn)行數(shù)據(jù)庫查詢,因?yàn)檫@有時(shí)可能會隨機(jī)導(dǎo)致測試失敗。所以你需要確保兩個(gè)線程不會同時(shí)訪問數(shù)據(jù)庫。特別是,這意味著在某些情況下(例如,剛剛點(diǎn)擊一個(gè)鏈接或提交一個(gè)表單之后),你可能需要檢查 Selenium 是否收到了響應(yīng),并且在繼續(xù)執(zhí)行進(jìn)一步的測試之前,檢查下一個(gè)頁面是否被加載。例如,讓 Selenium 等待直到在響應(yīng)中找到 ?<body>? HTML 標(biāo)簽(需要 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'))

這里的棘手之處在于,實(shí)際上并沒有頁面加載之類的東西,尤其是在服務(wù)器生成初始文檔后動態(tài)生成 HTML 的現(xiàn)代 Web 應(yīng)用程序中。 因此,檢查響應(yīng)中是否存在 ?<body>? 可能不一定適用于所有用例。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號