关于 pytest-django-patterns
面向 Django 项目的 pytest-django 测试模式指南,供开发者编写和组织测试。它强调先测试后实现,并提供数据库、视图、表单、模型及任务的测试方法。
主要功能
- 01RED-GREEN-REFACTOR 测试流程
- 02数据库标记与测试数据工厂
- 03按应用组织测试文件
- 04视图、表单和模型测试
- 05模拟外部服务与任务
适用场景
- 01为新功能采用测试驱动开发
- 02验证 HTMX 视图和权限行为
- 03检查表单验证与数据库变更
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ waldencui/ claude-code-django --skill pytest-django-patterns安装后在 Claude Code 中输入/pytest-django-patterns确认已加载。
详细介绍
pytest-django 测试模式
TDD 工作流(RED-GREEN-REFACTOR)
始终遵循此循环:
- RED:先编写一个描述预期行为的失败测试
- GREEN:编写最少量的代码,让测试通过
- REFACTOR:在保持测试通过的同时清理代码
- REPEAT:没有失败测试时,绝不编写生产代码
关键规则:实现功能或修复 Bug 时,先编写测试,再修改生产代码。
pytest-django 基本模式
数据库访问
- 对任何访问数据库的测试使用
@pytest.mark.django_db - 应用于整个模块:
pytestmark = pytest.mark.django_db - 每个测试结束后,事务都会自动回滚
测试数据的 Fixtures
模型使用 Factory Boy,设置使用 pytest fixtures:
-
Factories:使用真实的数据创建模型实例(
UserFactory())- 使用
factory.Sequence()为字段生成唯一值 - 使用
factory.Faker()生成逼真的伪数据 - 使用
factory.SubFactory()为外键赋值 - 使用
@factory.post_generation处理 M2M 关系
- 使用
-
Fixtures:用于设置客户端、身份验证状态或共享资源
clientfixture:Django 测试客户端- 创建
auth_clientfixture:使用client.force_login(user)发送经过身份验证的请求 - 在
conftest.py中定义,以便在多个测试文件中复用
测试组织
让测试结构与应用结构保持一致:
tests/
├── apps/
│ └── posts/
│ ├── test_models.py
│ ├── test_views.py
│ └── test_forms.py
├── factories.py
└── conftest.py
将相关测试分组到类中:
- 将类命名为
TestComponentName(例如TestPostListView) - 使用描述性名称命名测试方法:
test_<action>_<expected_outcome> - 使用
@pytest.mark.parametrize测试多个场景
测试内容
视图
- 状态码:正确的 HTTP 响应(200、404、302)
- 身份验证:已验证身份与匿名用户的行为
- 授权:用户只能访问自己的数据
- 上下文数据:传递给模板的对象正确
- 副作用:数据库变更、邮件发送、任务入队
- HTMX:检查
HTTP_HX_REQUEST请求头是否返回部分模板
表单
- 验证:有效数据通过验证,无效数据产生正确的错误
- 边界情况:空字段、最大长度、唯一约束
- Clean 方法:自定义验证逻辑正常运行
- 保存行为:对象正确创建或更新
模型
- 方法:
__str__和自定义方法返回预期值 - 管理器/QuerySet:自定义筛选正常工作
- 约束:数据库级验证得到强制执行
- 信号:保存前后钩子正确执行
Celery 任务
- 模拟外部调用:模拟 HTTP 请求、邮件发送等
- 只测试逻辑:不要测试实际的异步执行
- 幂等性:多次运行任务是安全的
Django 专用测试模式
测试 HTMX 响应
检查存在 HX-Request 请求头时是否渲染了部分模板:
- 向客户端请求传入
HTTP_HX_REQUEST="true" - 断言
response.templates包含部分模板名称
测试权限
创建已验证身份和匿名用户的客户端 fixtures:
- 测试未授权访问时的重定向或 403 响应
- 测试已授权访问时的成功响应
测试 QuerySet
验证查询是否高效:
- 使用 factories 创建测试数据
- 执行查询
- 断言返回或排除的对象正确
- 验证是否使用
select_related()/prefetch_related()加载了关联对象
使用模型实例测试表单
更新时向表单传入实例:
form = MyForm(data=new_data, instance=existing_obj)- 验证
form.save()会更新对象,而非创建对象
常见模式
参数化多个场景:
使用 @pytest.mark.parametrize("input,expected", [...]) 测试多种输入
模拟外部服务:
使用 mocker.patch() 避免实际发起 HTTP 调用、发送邮件或执行文件操作
检查数据库变更:
- 创建后断言
Model.objects.filter(...).exists() - 删除后断言
Model.objects.count() == expected - 使用
refresh_from_db()验证更新
测试错误处理:
- 无效的表单数据会产生正确的错误
- 操作失败会返回错误响应
- 用户会看到相应的错误消息
运行测试
uv run pytest # All tests
uv run pytest -x # Stop on first failure
uv run pytest --lf # Run last failed
uv run pytest -x --lf # Stop first, last failed only
uv run pytest -k "test_name" # Run tests matching pattern
uv run pytest tests/apps/posts/ # Specific directory
uv run pytest --cov=apps # With coverage report
常见陷阱
- 忘记添加
@pytest.mark.django_db:会导致出现“Database access not allowed”错误 - 不使用工厂:手动创建实例冗长且脆弱
- 测试实现细节:测试行为和结果,而非内部实现细节
- 跳过 TDD:在编写代码后再写测试,会让测试跟随实现,遗漏边界情况
- 过度模拟:模拟外部依赖,而非你自己的代码
- 测试框架代码:不要测试 Django 的 ORM、表单验证等;要测试你自己的逻辑
设置要求
在 pyproject.toml 中:
[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "config.settings.test"
python_files = ["test_*.py"]
addopts = ["--reuse-db", "-ra"]
在 conftest.py 中:
定义共享的 fixtures(auth_client、常用工厂等)
与其他技能集成
- systematic-debugging:修复错误时,先编写失败的测试来复现问题
- django-models:测试自定义管理器、QuerySet 和模型方法
- django-forms:测试表单验证、clean 方法和保存行为
- celery-patterns:使用模拟的外部依赖测试任务逻辑
评论
支持 Markdown