创建 Django 应用时会自动生成 tests.py。
大多数开发者都曾有过这样的想法。
“这个文件……可以删除吗?”
“到底要怎么用?”
其实 tests.py 是 Django 官方推荐的测试入口,
只要稍微熟悉一下,在重构、功能添加、版本升级时 会成为真正的重要保障。(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 测试在以下情况中尤为重要。
-
验证模型/业务逻辑
-
计算逻辑、状态变化、定制方法越复杂
-
一旦创建测试,以后修改时就可以安心重构
-
-
检查视图/URL/权限
-
根据认证状态响应代码是否不同
-
针对错误输入的适当重定向/错误处理
-
所需的上下文是否正确传递到模板
-
-
防止回归 bug
- 一旦发现 bug,通过 测试固定 后同样的问题不会再出现
-
确保版本升级的稳定性
-
在升级 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.TestCase或django.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 重现测试”防止回归
-
在生产中发生 bug
-
在本地编写重现测试(需失败)
-
修改代码
-
确认测试成功
-
将
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())
- 可以将表单的有效性检查规则以测试的形式留下。

8. 总结 – 别删 tests.py,好好写一次
一句话总结:
tests.py是“Django 应用的安全网”文件。
-
起初可能会觉得麻烦,
-
但一旦体验了 测试驱动开发的流程,
-
在重构/功能添加/版本升级时压力会大大减轻。
在实际项目中:
-
创建新功能时 – 至少添加 1-2 个测试
-
修复 bug 时 – 必须先编写重现该 bug 的测试
-
开始扩大项目时 – 进行
tests.py→tests/包重构
只要坚持这个模式,你就会有完全不同于“没有测试的 Django 项目”的开发体验。
目前没有评论。