撰寫並執行測試¶
本文件分為兩個主要部分。首先,我們將說明如何使用 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"')
當您執行測試時,測試工具的預設行為是找到任何名稱開頭為 test
的檔案中所有的測試案例類別(即 unittest.TestCase
的子類別),自動從這些測試案例類別建構測試套件,並執行該套件。
如需更多關於 unittest
的詳細資訊,請參閱 Python 文件。
測試應該放在哪裡?
預設的 startapp
範本會在新的應用程式中建立一個 tests.py
檔案。如果您的測試不多,這可能沒問題,但隨著您的測試套件成長,您可能會想要將其重組為測試套件,以便將測試分成不同的子模組,例如 test_models.py
、test_views.py
、test_forms.py
等。請隨意選擇您喜歡的任何組織架構。
警告
如果您的測試依賴資料庫存取,例如建立或查詢模型,請務必將測試類別建立為 django.test.TestCase
的子類別,而不是 unittest.TestCase
。
使用 unittest.TestCase
可以避免在交易中執行每個測試和清除資料庫的成本,但如果您的測試與資料庫互動,其行為會根據測試執行器執行它們的順序而有所不同。這可能會導致單元測試在單獨執行時通過,但在套件中執行時失敗。
執行測試¶
撰寫測試後,請使用專案的 manage.py
工具的 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 class
$ ./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 顯示棄用警告。Django 與許多其他 Python 函式庫一樣,使用這些警告來標記功能何時會被移除。它也可能會標記程式碼中嚴格來說沒有錯誤,但可以從更好的實作中受益的區域。
測試資料庫¶
需要資料庫的測試(即模型測試)不會使用您的「真實」(生產)資料庫。會為測試建立單獨的空白資料庫。
無論測試通過或失敗,當所有測試都執行完畢時,都會銷毀測試資料庫。
您可以使用 test --keepdb
選項來防止測試資料庫被銷毀。這將在執行之間保留測試資料庫。如果資料庫不存在,則會先建立它。也會應用任何移轉,以使其保持最新狀態。
如上一節所述,如果測試執行被強制中斷,則測試資料庫可能不會被銷毀。在下次執行時,系統會詢問您是否要重複使用或銷毀資料庫。使用 test --noinput
選項來取消該提示並自動銷毀資料庫。這在持續整合伺服器上執行測試時非常有用,例如,在這些伺服器上測試可能會因逾時而中斷。
預設的測試資料庫名稱是透過在 DATABASES
中每個 NAME
的值前面加上 test_
來建立的。當使用 SQLite 時,測試預設會使用記憶體中的資料庫(即,資料庫會在記憶體中建立,完全繞過檔案系統!)。DATABASES
中的 TEST
字典提供了許多設定來設定您的測試資料庫。例如,如果您想要使用不同的資料庫名稱,請在 DATABASES
中任何給定資料庫的 TEST
字典中指定 NAME
。
在 PostgreSQL 上,USER
也需要對內建的 postgres
資料庫具有讀取權限。
除了使用獨立的資料庫之外,測試執行器將會使用您設定檔中的所有資料庫設定:ENGINE
、USER
、HOST
等等。測試資料庫是由 USER
指定的使用者所建立,因此您需要確保該使用者帳戶在系統上有足夠的權限來建立新的資料庫。
若要精細控制測試資料庫的字元編碼,請使用 CHARSET
測試選項。如果您使用 MySQL,您也可以使用 COLLATION
選項來控制測試資料庫所使用的特定排序規則。請參閱設定文件,以了解這些和其他進階設定的詳細資訊。
如果使用 SQLite 的記憶體內資料庫,則會啟用共享快取,因此您可以編寫能夠在執行緒之間共享資料庫的測試。
在執行測試時,從您的生產資料庫中尋找資料?
如果您的程式碼在模組編譯時嘗試存取資料庫,這將在測試資料庫設定之前發生,可能會產生意想不到的結果。例如,如果您在模組層級的程式碼中有資料庫查詢,並且存在真實的資料庫,則生產資料可能會污染您的測試。無論如何,在您的程式碼中進行此類匯入時的資料庫查詢是個壞主意 - 請重新編寫您的程式碼,使其不執行此操作。
這也適用於 ready()
的自訂實作。
另請參閱
請參閱進階多資料庫測試主題。
測試的執行順序¶
為了保證所有 TestCase
程式碼都從乾淨的資料庫開始,Django 測試執行器會以以下方式重新排序測試
所有
TestCase
子類別會先執行。然後,所有其他基於 Django 的測試(基於
SimpleTestCase
的測試案例類別,包括TransactionTestCase
)將會執行,但不保證它們之間的特定順序,也不會強制執行。然後,任何其他可能在未將資料庫恢復到原始狀態的情況下修改資料庫的
unittest.TestCase
測試(包括 doctest)將會執行。
注意
新的測試順序可能會揭示測試案例順序中意想不到的依賴關係。對於依賴於給定 TransactionTestCase
測試在資料庫中留下的狀態的 doctest 來說,情況就是如此,它們必須更新才能夠獨立執行。
注意
在所有上述操作之前,會先排序載入測試時偵測到的失敗,以便更快地獲得回饋。這包括諸如找不到測試模組或因語法錯誤而無法載入的模組。
您可以使用 test --shuffle
和 --reverse
選項,在群組內隨機化和/或反轉執行順序。這有助於確保您的測試彼此獨立。
回滾模擬¶
在遷移中載入的任何初始資料僅在 TestCase
測試中可用,而不在 TransactionTestCase
測試中可用,此外,僅在支援交易的後端上可用(最重要的例外是 MyISAM)。對於依賴 TransactionTestCase
的測試(例如 LiveServerTestCase
和 StaticLiveServerTestCase
)也是如此。
Django 可以透過在 TestCase
或 TransactionTestCase
的主體中將 serialized_rollback
選項設定為 True
,為您在每個測試案例的基礎上重新載入該資料,但請注意,這會使該測試套件的速度降低約 3 倍。
第三方應用程式或針對 MyISAM 開發的應用程式將需要設定此選項;但是,一般而言,您應該針對交易式資料庫開發自己的專案,並對大多數測試使用 TestCase
,因此不需要此設定。
初始序列化通常非常快,但如果您希望將某些應用程式排除在此過程之外(並稍微加快測試執行速度),您可以將這些應用程式新增至 TEST_NON_SERIALIZED_APPS
。
為了防止序列化資料被載入兩次,設定 serialized_rollback=True
會在清除測試資料庫時停用 post_migrate
訊號。
其他測試條件¶
無論您設定檔中的 DEBUG
設定值為何,所有 Django 測試都會在 DEBUG
=False 的情況下執行。這是為了確保您程式碼的觀察輸出與生產環境中看到的內容相符。
快取不會在每次測試後清除,如果您在生產環境中執行測試,則執行 manage.py test fooapp
可以將測試中的資料插入到線上系統的快取中,因為與資料庫不同,不會使用單獨的「測試快取」。此行為在未來可能會更改。
了解測試輸出¶
當您執行測試時,您會看到許多訊息,因為測試執行器會自行準備。您可以使用命令列上的 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
程式庫的文件以了解詳細資訊。
請注意,對於任何數量的失敗測試(無論失敗是由錯誤、斷言失敗還是意外成功引起的),測試執行器指令碼的傳回碼都是 1。如果所有測試都通過,則傳回碼為 0。如果您在 shell 指令碼中使用測試執行器指令碼,並且需要在該層級測試成功或失敗,則此功能很有用。
加速測試¶
並行執行測試¶
只要您的測試已正確隔離,您就可以並行執行它們,以在多核心硬體上加快速度。請參閱 test --parallel
。
密碼雜湊¶
預設的密碼雜湊器在設計上相當慢。如果您在測試中驗證許多使用者,您可能需要使用自訂的設定檔,並將 PASSWORD_HASHERS
設定為更快的雜湊演算法
PASSWORD_HASHERS = [
"django.contrib.auth.hashers.MD5PasswordHasher",
]
如果有的話,請不要忘記在 PASSWORD_HASHERS
中包含 fixtures 中使用的任何雜湊演算法。
保留測試資料庫¶
test --keepdb
選項會在測試執行之間保留測試資料庫。它會略過建立和銷毀動作,這可以大大減少執行測試的時間。
避免媒體檔案的磁碟存取¶
InMemoryStorage
是一種避免媒體檔案磁碟存取的便捷方法。所有資料都保存在記憶體中,然後在測試執行後丟棄。