單元測試

Django 內建了自己的測試套件,位於程式碼庫的 tests 目錄中。我們的政策是確保所有測試都始終通過。

我們感謝任何對測試套件的貢獻!

Django 測試都使用 Django 隨附的測試基礎結構來測試應用程式。請參閱撰寫和執行測試以了解如何撰寫新的測試。

執行單元測試

快速入門

首先,在 GitHub 上 fork Django

第二,建立並啟動虛擬環境。如果您不熟悉如何執行此操作,請閱讀我們的貢獻教學

接下來,複製您的 fork、安裝一些需求,並執行測試

$ git clone https://github.com/YourGitHubName/django.git django-repo
$ cd django-repo/tests
$ python -m pip install -e ..
$ python -m pip install -r requirements/py3.txt
$ ./runtests.py
...\> git clone https://github.com/YourGitHubName/django.git django-repo
...\> cd django-repo\tests
...\> py -m pip install -e ..
...\> py -m pip install -r requirements\py3.txt
...\> runtests.py 

安裝需求可能需要您電腦尚未安裝的一些作業系統套件。您通常可以藉由搜尋錯誤訊息的最後一行左右來找出要安裝的套件。如果需要,請嘗試將您的作業系統新增至搜尋查詢中。

如果您在安裝需求時遇到問題,您可以跳過該步驟。請參閱執行所有測試以取得安裝可選測試相依性的詳細資訊。如果您沒有安裝可選的相依性,則需要它的測試將會被跳過。

執行測試需要一個定義要使用的資料庫的 Django 設定模組。為了幫助您開始,Django 提供並使用一個使用 SQLite 資料庫的範例設定模組。請參閱使用其他設定模組以了解如何使用其他設定模組來使用不同的資料庫執行測試。

有問題嗎?請參閱疑難排解以了解一些常見問題。

使用 tox 執行測試

Tox 是一種在不同的虛擬環境中執行測試的工具。Django 包括一個基本的 tox.ini,可自動執行我們的建置伺服器在提取請求上執行的一些檢查。若要執行單元測試和其他檢查(例如匯入排序文件拼字檢查工具程式碼格式化),請從 Django 原始碼樹中的任何位置安裝並執行 tox 命令

$ python -m pip install tox
$ tox
...\> py -m pip install tox
...\> tox

預設情況下,tox 會使用針對 SQLite 的綁定測試設定檔、blackblacken-docsflake8isort 和文件拼字檢查工具執行測試套件。除了本文件中其他地方提到的系統相依性之外,python3 命令必須在您的路徑上,並連結到適當的 Python 版本。預設環境的清單如下所示

$ tox -l
py3
black
blacken-docs
flake8>=3.7.0
docs
isort>=5.1.0
...\> tox -l
py3
black
blacken-docs
flake8>=3.7.0
docs
isort>=5.1.0

測試其他 Python 版本和資料庫後端

除了預設環境之外,tox 還支援為其他 Python 版本和其他資料庫後端執行單元測試。然而,由於 Django 的測試套件沒有為 SQLite 以外的資料庫後端綁定設定檔,因此您必須建立並提供您自己的測試設定。例如,若要使用 PostgreSQL 在 Python 3.10 上執行測試

$ tox -e py310-postgres -- --settings=my_postgres_settings
...\> tox -e py310-postgres -- --settings=my_postgres_settings

此命令會設定一個 Python 3.10 虛擬環境、安裝 Django 的測試套件相依性(包括 PostgreSQL 的相依性),並使用提供的引數 (在此情況下為 --settings=my_postgres_settings) 呼叫 runtests.py

本文件的其餘部分會顯示不使用 tox 執行測試的命令,但是,任何傳遞至 runtests.py 的選項也可以透過在引數清單前加上 -- 來傳遞至 tox,如上所示。

Tox 也會遵循 DJANGO_SETTINGS_MODULE 環境變數 (如果已設定)。例如,以下命令與上述命令等效

$ DJANGO_SETTINGS_MODULE=my_postgres_settings tox -e py310-postgres

Windows 使用者應使用

...\> set DJANGO_SETTINGS_MODULE=my_postgres_settings
...\> tox -e py310-postgres

執行 JavaScript 測試

Django 包含一組JavaScript 單元測試,用於某些 contrib 應用程式中的函式。預設情況下,tox 不會執行 JavaScript 測試,因為它們需要安裝 Node.js,而且對於大多數修補程式而言並非必要。若要使用 tox 執行 JavaScript 測試

$ tox -e javascript
...\> tox -e javascript

此命令會執行 npm install 以確保測試需求是最新的,然後執行 npm test

使用 django-docker-box 執行測試

django-docker-box 可讓您在所有支援的資料庫和 Python 版本中執行 Django 的測試套件。請參閱django-docker-box 專案頁面,以取得安裝和使用說明。

使用其他 settings 模組

包含的設定模組 (tests/test_sqlite.py) 可讓您使用 SQLite 執行測試套件。如果您想要使用其他資料庫執行測試,則需要定義自己的設定檔。某些測試,例如針對 contrib.postgres 的測試,是特定於特定資料庫後端的,如果使用不同的後端執行,則會被跳過。某些測試會被跳過,或者在特定資料庫後端上預期會失敗(請參閱每個後端的 DatabaseFeatures.django_test_skipsDatabaseFeatures.django_test_expected_failures)。

若要使用不同的設定執行測試,請確保模組位於您的 PYTHONPATH 上,並使用 --settings 傳遞模組。

任何測試設定模組中的 DATABASES 設定都需要定義兩個資料庫

  • 一個 default 資料庫。此資料庫應使用您想要用於主要測試的後端。

  • 一個別名為 other 的資料庫。other 資料庫用於測試查詢是否可以導向至不同的資料庫。此資料庫應使用與 default 相同的後端,而且必須有不同的名稱。

如果您使用的是不是 SQLite 的後端,則需要為每個資料庫提供其他詳細資訊

  • USER 選項需要指定資料庫的現有使用者帳戶。該使用者需要執行 CREATE DATABASE 的權限,以便可以建立測試資料庫。

  • PASSWORD 選項需要提供指定之 USER 的密碼。

測試資料庫會將 test_ 前置到 DATABASES 中定義的資料庫之 NAME 設定的值來取得其名稱。這些測試資料庫會在測試完成時刪除。

您還需要確保您的資料庫使用 UTF-8 作為預設字元集。如果您的資料庫伺服器未使用 UTF-8 作為預設字元集,則需要在適用資料庫的測試設定字典中包含 CHARSET 的值。

僅執行部分測試

Django 的整個測試套件需要一些時間才能執行,而且如果說您只是將一個測試新增至 Django,而您想要快速執行該測試而不執行其他所有測試,則執行每個測試可能會是多餘的。您可以透過在命令列上的 runtests.py 中附加測試模組的名稱來執行單元測試的子集。

例如,如果您只想執行一般關聯和國際化的測試,請輸入

$ ./runtests.py --settings=path.to.settings generic_relations i18n
...\> runtests.py --settings=path.to.settings generic_relations i18n

您要如何找出個別測試的名稱?請查看 tests/,那裡的每個目錄名稱都是測試的名稱。

如果您只想執行特定類別的測試,您可以指定個別測試類別的路徑清單。例如,若要執行 i18n 模組的 TranslationTests,請輸入

$ ./runtests.py --settings=path.to.settings i18n.tests.TranslationTests
...\> runtests.py --settings=path.to.settings i18n.tests.TranslationTests

除此之外,您可以使用下列方式指定個別的測試方法

$ ./runtests.py --settings=path.to.settings i18n.tests.TranslationTests.test_lazy_objects
...\> runtests.py --settings=path.to.settings i18n.tests.TranslationTests.test_lazy_objects

您可以使用 --start-at 選項從指定的頂層模組開始執行測試。例如

$ ./runtests.py --start-at=wsgi
...\> runtests.py --start-at=wsgi

您也可以使用 --start-after 選項,從指定的頂層模組之後開始執行測試。例如:

$ ./runtests.py --start-after=wsgi
...\> runtests.py --start-after=wsgi

請注意,--reverse 選項不會影響 --start-at--start-after 選項。此外,這些選項不能與測試標籤一起使用。

執行 Selenium 測試

某些測試需要 Selenium 和網頁瀏覽器。若要執行這些測試,您必須安裝 selenium 套件,並使用 --selenium=<BROWSERS> 選項執行測試。例如,如果您已安裝 Firefox 和 Google Chrome:

$ ./runtests.py --selenium=firefox,chrome
...\> runtests.py --selenium=firefox,chrome

請參閱 selenium.webdriver 套件以取得可用瀏覽器的清單。

指定 --selenium 會自動設定 --tags=selenium,只執行需要 selenium 的測試。

某些瀏覽器(例如 Chrome 或 Firefox)支援無頭測試,這可以更快更穩定。新增 --headless 選項以啟用此模式。

若要測試管理 UI 的變更,可以啟用 --screenshots 選項來執行 selenium 測試。螢幕截圖將會儲存到 tests/screenshots/ 目錄。

若要定義在 selenium 測試期間應何時擷取螢幕截圖,測試類別必須使用 @django.test.selenium.screenshot_cases 裝飾器,並帶有支援的螢幕截圖類型清單("desktop_size""mobile_size""small_screen_size""rtl""dark""high_contrast")。然後,它可以在想要的位置呼叫 self.take_screenshot("unique-screenshot-name") 來產生螢幕截圖。例如:

from django.test.selenium import SeleniumTestCase, screenshot_cases
from django.urls import reverse


class SeleniumTests(SeleniumTestCase):
    @screenshot_cases(["desktop_size", "mobile_size", "rtl", "dark", "high_contrast"])
    def test_login_button_centered(self):
        self.selenium.get(self.live_server_url + reverse("admin:login"))
        self.take_screenshot("login")
        ...

這會產生登入頁面的多個螢幕截圖 - 一個用於桌面螢幕,一個用於行動螢幕,一個用於桌面上的由右至左語言,一個用於桌面上的黑暗模式,以及一個在使用 Chrome 時用於桌面上的高對比模式。

在 Django 5.1 中變更

新增了 --screenshots 選項和 @screenshot_cases 裝飾器。

執行所有測試

如果您想要執行完整的測試套件,您需要安裝一些相依性:

您可以在 Django 原始碼樹狀結構的 tests/requirements 目錄內的 pip requirements 檔案中找到這些相依性,並像這樣安裝它們:

$ python -m pip install -r tests/requirements/py3.txt
...\> py -m pip install -r tests\requirements\py3.txt

如果在安裝期間遇到錯誤,您的系統可能缺少一個或多個 Python 套件的相依性。請參閱失敗套件的文件或在網路上搜尋您遇到的錯誤訊息。

您也可以使用 oracle.txtmysql.txtpostgres.txt 來安裝您選擇的資料庫配接器。

如果您想要測試 memcached 或 Redis 快取後端,您還需要定義一個指向您的 memcached 或 Redis 執行個體的 CACHES 設定。

若要執行 GeoDjango 測試,您需要設定空間資料庫並安裝地理空間程式庫

這些相依性中的每一個都是選用的。如果您缺少任何一個,相關的測試將會被跳過。

若要執行某些自動重新載入測試,您需要安裝 Watchman 服務。

程式碼覆蓋率

我們鼓勵貢獻者在測試套件上執行覆蓋率,以找出需要額外測試的區域。覆蓋率工具的安裝和使用說明於測試程式碼覆蓋率中。

若要使用標準測試設定在 Django 測試套件上執行覆蓋率:

$ coverage run ./runtests.py --settings=test_sqlite
...\> coverage run runtests.py --settings=test_sqlite

執行覆蓋率後,執行以下命令來合併所有覆蓋率統計資訊:

$ coverage combine
...\> coverage combine

之後,執行以下命令來產生 html 報表:

$ coverage html
...\> coverage html

在執行 Django 測試的覆蓋率時,包含的 .coveragerc 設定檔會定義 coverage_html 作為報表的輸出目錄,並且還會排除數個與結果不相關的目錄(測試程式碼或包含在 Django 中的外部程式碼)。

Contrib 應用程式

contrib 應用程式的測試可以在 tests/ 目錄中找到,通常位於 <app_name>_tests 下。例如,contrib.auth 的測試位於 tests/auth_tests

疑難排解

測試套件在 main 分支上掛起或顯示失敗

請確定您擁有支援的 Python 版本的最新點版本,因為較早的版本中經常存在可能導致測試套件失敗或掛起的錯誤。

macOS (High Sierra 和更新版本) 上,您可能會看到記錄此訊息,之後測試會掛起:

objc[42074]: +[__NSPlaceholderDate initialize] may have been in progress in
another thread when fork() was called.

若要避免此問題,請設定 OBJC_DISABLE_INITIALIZE_FORK_SAFETY 環境變數,例如:

$ OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES ./runtests.py

或將 export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES 新增至您的 shell 啟動檔案(例如,~/.profile)。

許多測試失敗,並出現 UnicodeEncodeError

如果未安裝 locales 套件,某些測試將會失敗,並出現 UnicodeEncodeError

例如,您可以在基於 Debian 的系統上,執行以下命令來解決此問題:

$ apt-get install locales
$ dpkg-reconfigure locales

您可以透過設定 shell 的地區設定來解決 macOS 系統的此問題:

$ export LANG="en_US.UTF-8"
$ export LC_ALL="en_US.UTF-8"

執行 locale 命令來確認變更。或者,將這些 export 命令新增至您的 shell 啟動檔案(例如,Bash 的 ~/.bashrc),以避免必須重新輸入它們。

僅在組合時失敗的測試

如果測試在單獨執行時通過,但在整個套件中失敗,我們有一些工具可協助分析問題。

runtests.py--bisect 選項將會執行失敗的測試,同時在每次迭代時將其與之一起執行的測試集減半,通常可以找出可能與失敗相關的少量測試。

例如,假設單獨執行的失敗測試是 ModelTest.test_eq,那麼使用:

$ ./runtests.py --bisect basic.tests.ModelTest.test_eq
...\> runtests.py --bisect basic.tests.ModelTest.test_eq

將會嘗試判斷會干擾指定測試的測試。首先,測試會與測試套件的前半部分一起執行。如果發生失敗,則測試套件的前半部分會分為兩組,然後每組都與指定的測試一起執行。如果測試套件的前半部分沒有失敗,則測試套件的後半部分會與指定的測試一起執行,並如先前所述適當地分割。該過程會重複,直到將失敗測試的集合最小化。

--pair 選項會將指定的測試與套件中的每個其他測試一起執行,讓您檢查是否有另一個測試具有導致失敗的副作用。因此:

$ ./runtests.py --pair basic.tests.ModelTest.test_eq
...\> runtests.py --pair basic.tests.ModelTest.test_eq

會將 test_eq 與每個測試標籤配對。

使用 --bisect--pair 時,如果您已經懷疑哪些案例可能導致失敗,您可以透過在第一個案例之後指定更多測試標籤來限制要交叉分析的測試。

$ ./runtests.py --pair basic.tests.ModelTest.test_eq queries transactions
...\> runtests.py --pair basic.tests.ModelTest.test_eq queries transactions

您也可以使用 --shuffle--reverse 選項以隨機或反向順序執行任何測試集。這可以協助驗證以不同順序執行測試是否不會造成任何問題。

$ ./runtests.py basic --shuffle
$ ./runtests.py basic --reverse
...\> runtests.py basic --shuffle
...\> runtests.py basic --reverse

查看測試期間執行的 SQL 查詢

如果您想要檢查失敗測試中執行的 SQL,您可以使用 --debug-sql 選項開啟SQL 記錄。如果您將此選項與 --verbosity=2 結合使用,將會輸出所有 SQL 查詢。

$ ./runtests.py basic --debug-sql
...\> runtests.py basic --debug-sql

查看測試失敗的完整追溯

預設情況下,測試會以平行方式執行,每個核心一個進程。然而,當測試以平行方式執行時,您只會看到任何測試失敗的截斷回溯(truncated traceback)。您可以使用 --parallel 選項來調整此行為。

$ ./runtests.py basic --parallel=1
...\> runtests.py basic --parallel=1

您也可以使用 DJANGO_TEST_PROCESSES 環境變數來達到相同的目的。

撰寫測試的訣竅

隔離模型註冊

為了避免污染全域的 apps 註冊表,並防止不必要的表格建立,在測試方法中定義的模型應該綁定到一個暫時的 Apps 實例。為此,請使用 isolate_apps() 裝飾器。

from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps


class TestModelDefinition(SimpleTestCase):
    @isolate_apps("app_label")
    def test_model_definition(self):
        class TestModel(models.Model):
            pass

        ...

設定 app_label

在測試方法中定義的且沒有明確 app_label 的模型,會自動被賦予其測試類別所在的應用程式標籤。

為了確保在 isolate_apps() 實例上下文內定義的模型被正確安裝,您應該將目標 app_label 的集合作為參數傳遞。

tests/app_label/tests.py
from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps


class TestModelDefinition(SimpleTestCase):
    @isolate_apps("app_label", "other_app_label")
    def test_model_definition(self):
        # This model automatically receives app_label='app_label'
        class TestModel(models.Model):
            pass

        class OtherAppModel(models.Model):
            class Meta:
                app_label = "other_app_label"

        ...
返回頂部