當你創建 Django 應用時,會自動生成 tests.py
大多數開發者都有過這樣的想法。

“這個檔案……是不是可以刪掉?”
“究竟要怎麼用呢?”

事實上,tests.pyDjango 官方推薦的測試入口點
如果稍微熟悉一下,當你進行重構、添加功能或版本升級時,它將成為真正的保障。(Django Project

在這篇文章中,我們將:

  • 探討 tests.py 是什麼檔案

  • 如何編寫和執行它

  • 在什麼情況下特別有用

  • 實際使用範例

一次性整理明瞭。


1. tests.py 是什麼檔案?



Django 使用基於 Python 標準庫的 unittest 測試框架

  • 創建繼承自 django.test.TestCase 的測試類別

  • 在內部編寫以 test_ 開頭的方法

  • 通過 ./manage.py test 命令自動查找並執行。

使用 startapp 創建應用時,會自動生成 tests.py

  • 小型項目 → 只需一個 tests.py 即可

  • 如果規模較大 → 建議將其拆分為 tests/ 包(test_models.pytest_views.py 等)來管理,這在 Django 文檔中也有提到。

因此,可以理解為“應用單位測試的基本位置”


2. 何時有用?(為什麼一定要使用測試)

Django 測試在以下情況下特別熠熠生輝。

  1. 模型/業務邏輯驗證

    • 計算邏輯、狀態變更、自定義方法越複雜

    • 一旦建立測試後,未來修改時可以安心進行重構

  2. 視圖/URL/權限檢查

    • 根據身份驗證狀態檢查響應碼是否正確

    • 對於不正確的輸入有是否正確的重定向 / 錯誤處理

    • 確保所需的上下文正確傳遞到模板

  3. 防止回歸錯誤

    • 一旦抓住錯誤,用測試固定下來,同樣問題不會再出現
  4. 提升升級穩定性

    • 在升級 Django 或庫版本時

    • 可以先運行 ./manage.py test 再開始


3. 最基本的 tests.py 範例



3-1. 模型測試

# myapp/tests.py
from django.test import TestCase
from .models import Article

class ArticleModelTests(TestCase):
    def setUp(self):
        self.article = Article.objects.create(
            title="測試標題",
            content="內容",
            views=0,
        )

    def test_increase_views(self):
        # given
        self.assertEqual(self.article.views, 0)

        # when
        self.article.increase_views()

        # then
        self.assertEqual(self.article.views, 1)
  • TestCase 是擴展自 unittest.TestCase 的 Django 類。

  • 每個測試都是以 事務單元隔離的,每個測試後資料庫均會回滾,互不干擾。


4. 如何測試視圖/URL – 使用 Client

Django 提供了專用的測試 HTTP 客戶端 django.test.Client
不啟動實際伺服器的情況下,也能發送 GET/POST 請求並檢查響應。

4-1. 簡單的視圖測試

# myapp/tests.py
from django.test import TestCase
from django.urls import reverse
from .models import Article

class ArticleViewTests(TestCase):
    def setUp(self):
        self.article = Article.objects.create(
            title="文章 1",
            content="內容",
            views=0,
        )

    def test_article_detail_page_returns_200(self):
        url = reverse("article_detail", args=[self.article.id])

        response = self.client.get(url)

        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "文章 1")

這裡的關鍵在於:

  • self.client : 是 TestCase 提供的測試客戶端實例

  • reverse("article_detail", args=[...]) : 使用 URL 名稱進行查詢,而不是硬編碼 URL

  • assertContains : 檢查響應正文是否包含特定字符串

4-2. 登錄/權限檢查測試

from django.contrib.auth import get_user_model

class ArticlePermissionTests(TestCase):
    def setUp(self):
        self.user = get_user_model().objects.create_user(
            username="tester",
            password="pass1234",
        )
        self.article = Article.objects.create(
            title="秘密",
            content="秘密文章",
            views=0,
        )

    def test_anonymous_user_redirected_to_login(self):
        url = reverse("article_edit", args=[self.article.id])

        response = self.client.get(url)

        self.assertEqual(response.status_code, 302)
        self.assertIn("/accounts/login", response["Location"])

    def test_logged_in_user_can_access_edit_page(self):
        self.client.login(username="tester", password="pass1234")
        url = reverse("article_edit", args=[self.article.id])

        response = self.client.get(url)

        self.assertEqual(response.status_code, 200)

這樣的話:

  • 檢查匿名使用者是否被重定向

  • 檢查已登錄的用戶是否可以正常訪問

可以早期過濾掉權限相關的錯誤


5. 什麼時候/怎麼執行 tests.py

5-1. 執行全部測試

$ python manage.py test
  • 會自動查找並執行當前目錄下的 test*.py 檔案中
    unittest.TestCasedjango.test.TestCase 的子類。

5-2. 僅執行特定應用

$ python manage.py test myapp

5-3. 執行特定模塊/類別/方法

# 整個 myapp.tests 模塊
$ python manage.py test myapp.tests

# 特定 TestCase
$ python manage.py test myapp.tests.ArticleViewTests

# 特定測試方法
$ python manage.py test myapp.tests.ArticleViewTests.test_article_detail_page_returns_200

在開發過程中,建議養成快速測試修改部分的習慣


6. 擴展 tests.py 成 tests 包

隨著項目增大,僅用 tests.py 已無法應對。
Django 文檔中也建議將其拆分成包結構。(Django Project

例如:

myapp/
    tests/
        __init__.py
        test_models.py
        test_views.py
        test_forms.py
        test_api.py

每個檔案內:

# myapp/tests/test_models.py
from django.test import TestCase
from myapp.models import Article

class ArticleModelTests(TestCase):
    ...

這樣分開後執行與此前相同。

$ python manage.py test myapp   # 全部
$ python manage.py test myapp.tests.test_models  # 僅模型測試

7. 幾個實戰使用案例

7-1. 用“重現錯誤的測試”來防止回歸

  1. 在生產環境中出現錯誤

  2. 在本地創建重現錯誤的測試(應失敗)

  3. 修改代碼

  4. 檢查測試是否成功

  5. git提交測試+修改的代碼

這樣在之後重構時,就不會重現同樣的錯誤。

7-2. 固定 API 回應格式

在前端協作時,API 回應格式非常重要。

class ArticleApiTests(TestCase):
    def setUp(self):
        self.article = Article.objects.create(
            title="API 標題",
            content="內容",
            views=0,
        )

    def test_article_detail_api_response(self):
        url = reverse("api:article-detail", args=[self.article.id])

        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

        data = response.json()
        self.assertEqual(data["id"], self.article.id)
        self.assertIn("title", data)
        self.assertIn("content", data)
  • 明確聲明回應中必須包含的字段

  • 若字段名稱不小心改動或移除,則測試會直接失敗

7-3. 測試表單驗證邏輯

from .forms import ArticleForm

class ArticleFormTests(TestCase):
    def test_title_is_required(self):
        form = ArticleForm(data={"title": "", "content": "內容"})

        self.assertFalse(form.is_valid())
        self.assertIn("title", form.errors)

    def test_valid_form(self):
        form = ArticleForm(data={"title": "標題", "content": "內容"})

        self.assertTrue(form.is_valid())
  • 表單的有效性檢查規則可以像文檔一樣用測試保留下來。

撰寫 test py 的 Django 開發者

8. 總結 – 不要刪除 tests.py,只需一次好好使用它

簡單總結如下:

tests.py 是為“Django 應用創造安全網”的檔案。

  • 雖然最初會覺得麻煩,

  • 但一旦體驗到基於測試進行開發的流程

  • 在進行重構/添加功能/版本升級時壓力會減少得多。

在實際項目中:

  1. 創建新功能時 – 至少添加 1~2 個測試

  2. 修復錯誤時 – 必須先編寫重現該錯誤的測試

  3. 開始擴大項目時 – 將 tests.py 重構為 tests/

只要遵循這個模式,就會擁有與“沒有測試的 Django 項目”完全不同的開發體驗。