Python
测试、调试与日志
发布于 2026年7月23日
测试、调试与日志
测试保证修改没有破坏既有行为,日志帮助定位无法在本地复现的问题。本篇从模型、表单、视图测试讲到调试方法和生产日志。
一、测试什么
优先测试项目自己的业务规则,而不是验证 Django 框架本身:
- 模型方法、约束和状态转换。
- 表单自定义校验。
- 认证、权限和对象归属。
- 关键 CRUD 与重定向。
- API 状态码和响应结构。
- 容易回归的历史缺陷。
二、模型测试
notes/tests/test_models.py:
from django.contrib.auth import get_user_model
from django.test import TestCase
from notes.models import Message
User = get_user_model()
class MessageModelTests(TestCase):
def setUp(self):
self.user = User.objects.create_user(
username="lin",
password="test-password-123",
)
def test_string_representation_uses_name(self):
message = Message(owner=self.user, name="第一条", content="正文")
self.assertEqual(str(message), "第一条")
def test_default_status_is_draft(self):
message = Message.objects.create(
owner=self.user,
name="草稿",
content="正文",
)
self.assertEqual(message.status, Message.Status.DRAFT)
TestCase 会为每个测试隔离数据库变化。测试之间不能依赖执行顺序,也不应共享上一个测试留下的数据。
三、表单测试
from django.test import SimpleTestCase
from notes.forms import MessageForm
class MessageFormTests(SimpleTestCase):
def test_content_must_have_at_least_ten_characters(self):
form = MessageForm(data={
"name": "测试",
"content": "太短",
"status": "draft",
})
self.assertFalse(form.is_valid())
self.assertIn("content", form.errors)
不访问数据库的测试可使用 SimpleTestCase,通常更轻量。
四、视图与权限测试
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse
from notes.models import Message
User = get_user_model()
class MessageViewTests(TestCase):
def setUp(self):
self.owner = User.objects.create_user("owner", password="pass-12345")
self.other = User.objects.create_user("other", password="pass-12345")
self.message = Message.objects.create(
owner=self.owner,
name="私有留言",
content="这是一条测试留言内容",
)
def test_anonymous_user_is_redirected_from_update(self):
response = self.client.get(
reverse("notes:update", kwargs={"pk": self.message.pk})
)
self.assertEqual(response.status_code, 302)
def test_other_user_cannot_update_message(self):
self.client.force_login(self.other)
response = self.client.get(
reverse("notes:update", kwargs={"pk": self.message.pk})
)
self.assertEqual(response.status_code, 404)
def test_owner_can_update_message(self):
self.client.force_login(self.owner)
response = self.client.post(
reverse("notes:update", kwargs={"pk": self.message.pk}),
{
"name": "已修改",
"content": "修改后的留言内容足够长",
"status": "draft",
},
)
self.assertRedirects(
response,
reverse("notes:detail", kwargs={"pk": self.message.pk}),
)
self.message.refresh_from_db()
self.assertEqual(self.message.name, "已修改")
权限测试至少包含匿名用户、无权用户和有权用户三类情况。
五、测试查询数量
def test_message_list_has_no_n_plus_one(self):
Message.objects.bulk_create([
Message(owner=self.owner, name=f"留言 {i}", content="测试内容")
for i in range(10)
])
with self.assertNumQueries(3):
response = self.client.get(reverse("notes:list"))
self.assertEqual(response.status_code, 200)
精确查询数会随认证、分页和模板变化,应在真正关心 N+1 回归的页面使用,不必给所有测试增加脆弱断言。
六、运行测试
# 全部测试
python manage.py test
# 某个应用
python manage.py test notes
# 某个测试类
python manage.py test notes.tests.test_views.MessageViewTests
项目增大后可引入 pytest、pytest-django 和工厂库,但先理解 Django 自带测试工具,避免工具掩盖基础概念。
七、调试思路
遇到异常时按以下顺序:
- 阅读异常类型和堆栈最后几层。
- 找到第一个属于自己项目的文件与行号。
- 检查输入数据、对象状态和实际执行路径。
- 缩小到最小复现步骤。
- 为缺陷补一个失败测试,再修复代码。
开发环境可以暂停并检查现场:
breakpoint()
常用诊断命令:
python manage.py check
python manage.py showmigrations
python manage.py shell
不要在生产响应中显示调试页面,也不要把完整请求体、密码和令牌打印出来。
八、日志配置
settings.py:
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"verbose": {
"format": "{asctime} {levelname} {name} {message}",
"style": "{",
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "verbose",
},
},
"loggers": {
"django": {
"handlers": ["console"],
"level": "INFO",
"propagate": False,
},
"notes": {
"handlers": ["console"],
"level": "INFO",
"propagate": False,
},
},
}
业务代码:
import logging
logger = logging.getLogger(__name__)
logger.info("message_published", extra={"message_id": message.pk})
生产环境推荐结构化日志,至少包含时间、级别、模块、请求 ID、用户 ID(如适用)和关键对象 ID。不要记录密码、会话 Cookie、银行卡、完整令牌或不必要的个人信息。
九、本篇检查清单
- 能测试模型、表单、视图和权限。
- 测试之间相互独立,不依赖执行顺序。
- 能用失败测试复现缺陷。
- 会从异常堆栈定位自己项目中的第一处问题。
- 日志包含排查所需上下文,但不泄露敏感信息。
上一篇:配置、中间件、信号与管理命令 | 下一篇:使用 DRF 开发 RESTful API