W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗(yàn)值獎勵
一般的 Python 單元測試類都會擴(kuò)展一個(gè)基類 ?unittest.TestCase
?。Django 提供了這個(gè)基類的一些擴(kuò)展。
Django 單元測試類的層次結(jié)構(gòu)
你可以將一個(gè)普通的 ?unittest.TestCase
? 轉(zhuǎn)換為任何一個(gè)子類:將你的測試基類從 ?unittest.TestCase
? 改為子類。所有標(biāo)準(zhǔn)的 Python 單元測試功能都將是可用的,并且它將被一些有用的附加功能所增強(qiáng),如下面每節(jié)所述。
unittest.TestCase? 的一個(gè)子類,增加了以下功能:
一些有用的斷言,例如:
如果你的測試進(jìn)行任何數(shù)據(jù)庫查詢,請使用子類 ?TransactionTestCase
?或 ?TestCase
?。
?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
?繼承自 ?SimpleTestCase
?以增加一些數(shù)據(jù)庫特有的功能:
fixtures
? 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
?。
這是 Django 中最常用的編寫測試的類。它繼承自 ?TransactionTestCase
?(以及擴(kuò)展自 ?SimpleTestCase
?)。如果你的 Django 應(yīng)用程序不使用數(shù)據(jù)庫,就使用 ?SimpleTestCase
?。
atomic()
? 塊中封裝測試:一個(gè)用于整個(gè)類,一個(gè)用于每個(gè)測試。因此,如果你想測試一些特定的數(shù)據(jù)庫事務(wù)行為,可以使用 ?TransactionTestCase
?它還提供了另一種方法:
上文所述的類級 ?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)勢。
返回一個(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
?和 ?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>
? 可能不一定適用于所有用例。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: