创建 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.py, test_views.py 等)进行管理,Django 文档中也有此建议。

也就是说,可以理解为 “进行应用级别测试的基本位置”


2. 什么时候有用?(为什么要使用测试)

Django 测试在以下情况中尤为重要。

  1. 验证模型/业务逻辑

    • 计算逻辑、状态变化、定制方法越复杂

    • 一旦创建测试,以后修改时就可以安心重构

  2. 检查视图/URL/权限

    • 根据认证状态响应代码是否不同

    • 针对错误输入的适当重定向/错误处理

    • 所需的上下文是否正确传递到模板

  3. 防止回归 bug

    • 一旦发现 bug,通过 测试固定 后同样的问题不会再出现
  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
无需启动实际服务器即可向 URL 发送 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)

通过这样的方式:

  • 检查匿名用户是否会重定向到登录页面

  • 检查登录用户是否可以正常访问编辑页面

可以及时筛选出与 权限相关的 bug


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. 用“bug 重现测试”防止回归

  1. 在生产中发生 bug

  2. 在本地编写重现测试(需失败)

  3. 修改代码

  4. 确认测试成功

  5. git测试和修复代码一起提交

如此一来,未来重构时相同的 bug 不会再出现。

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())
  • 可以将表单的有效性检查规则以测试的形式留下。

编写 tests.py 的 Django 开发者

8. 总结 – 别删 tests.py,好好写一次

一句话总结:

tests.py 是“Django 应用的安全网”文件。

  • 起初可能会觉得麻烦,

  • 但一旦体验了 测试驱动开发的流程

  • 在重构/功能添加/版本升级时压力会大大减轻。

在实际项目中:

  1. 创建新功能时 – 至少添加 1-2 个测试

  2. 修复 bug 时 – 必须先编写重现该 bug 的测试

  3. 开始扩大项目时 – 进行 tests.pytests/ 包重构

只要坚持这个模式,你就会有完全不同于“没有测试的 Django 项目”的开发体验。