pytest-django

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

pytest-django

pytest-django

Deep Knowledge: Use
mcp__documentation__fetch_docs
with technology:
pytest-django
for comprehensive documentation on all fixtures, markers, and advanced patterns.
深度知识:使用
mcp__documentation__fetch_docs
并指定技术为
pytest-django
,可获取所有fixture、标记和高级模式的完整文档。

Setup

设置

toml
undefined
toml
undefined

pyproject.toml

pyproject.toml

[tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "myproject.settings.test" python_files = ["tests.py", "test_.py", "_tests.py"]
undefined
[tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "myproject.settings.test" python_files = ["tests.py", "test_.py", "_tests.py"]
undefined

@pytest.mark.django_db Options

@pytest.mark.django_db 选项

python
@pytest.mark.django_db                               # basic DB access, rollback
@pytest.mark.django_db(transaction=True)             # real commits (TransactionTestCase)
@pytest.mark.django_db(transaction=True, reset_sequences=True)  # reset auto-increment
@pytest.mark.django_db(databases=["default", "analytics"])      # multiple DBs
@pytest.mark.django_db(databases="__all__")          # all databases
@pytest.mark.django_db(serialized_rollback=True)     # for data migration tests
python
@pytest.mark.django_db                               # 基础数据库访问,自动回滚
@pytest.mark.django_db(transaction=True)             # 真实提交(TransactionTestCase)
@pytest.mark.django_db(transaction=True, reset_sequences=True)  # 重置自增序列
@pytest.mark.django_db(databases=["default", "analytics"])      # 多数据库
@pytest.mark.django_db(databases="__all__")          # 所有数据库
@pytest.mark.django_db(serialized_rollback=True)     # 用于数据迁移测试

Core Fixtures

核心Fixture

python
def test_basic(db):               # DB access fixture (use in other fixtures)
    pass

def test_client(client):          # django.test.Client
    response = client.get("/")
    assert response.status_code == 200

def test_admin(admin_client):     # pre-logged-in superuser client
    response = admin_client.get("/admin/")
    assert response.status_code == 200

def test_rf(rf, admin_user):      # RequestFactory (bypasses middleware)
    request = rf.get("/items/")
    request.user = admin_user
    response = my_view(request)

def test_settings(settings):      # modify settings, auto-reverted
    settings.DEBUG = True
    settings.CACHES = {"default": {"BACKEND": "...DummyCache"}}

def test_mail(mailoutbox):        # access sent emails
    send_mail("Subject", "Body", "from@x.com", ["to@x.com"])
    assert len(mailoutbox) == 1
    assert mailoutbox[0].subject == "Subject"

def test_user(django_user_model): # AUTH_USER_MODEL
    user = django_user_model.objects.create_user("alice", password="pass")
    assert user.check_password("pass")
python
def test_basic(db):               # 数据库访问fixture(可在其他fixture中使用)
    pass

def test_client(client):          # django.test.Client
    response = client.get("/")
    assert response.status_code == 200

def test_admin(admin_client):     # 已登录的超级用户客户端
    response = admin_client.get("/admin/")
    assert response.status_code == 200

def test_rf(rf, admin_user):      # RequestFactory(绕过中间件)
    request = rf.get("/items/")
    request.user = admin_user
    response = my_view(request)

def test_settings(settings):      # 修改配置,自动还原
    settings.DEBUG = True
    settings.CACHES = {"default": {"BACKEND": "...DummyCache"}}

def test_mail(mailoutbox):        # 查看已发送邮件
    send_mail("Subject", "Body", "from@x.com", ["to@x.com"])
    assert len(mailoutbox) == 1
    assert mailoutbox[0].subject == "Subject"

def test_user(django_user_model): # AUTH_USER_MODEL
    user = django_user_model.objects.create_user("alice", password="pass")
    assert user.check_password("pass")

Authentication Patterns

认证模式

python
def test_force_login(client, django_user_model):
    user = django_user_model.objects.create_user("alice", password="pass")
    client.force_login(user)            # fastest, no password check
    response = client.get("/private/")
    assert response.status_code == 200

def test_client_login(client, django_user_model):
    django_user_model.objects.create_user("alice", password="pass")
    client.login(username="alice", password="pass")
    response = client.get("/private/")
    assert response.status_code == 200
python
def test_force_login(client, django_user_model):
    user = django_user_model.objects.create_user("alice", password="pass")
    client.force_login(user)            # 最快方式,无需密码校验
    response = client.get("/private/")
    assert response.status_code == 200

def test_client_login(client, django_user_model):
    django_user_model.objects.create_user("alice", password="pass")
    client.login(username="alice", password="pass")
    response = client.get("/private/")
    assert response.status_code == 200

DRF APIClient

DRF APIClient

python
import pytest
from rest_framework.test import APIClient
from rest_framework import status

@pytest.fixture
def api_client():
    return APIClient()

@pytest.fixture
def auth_client(api_client, django_user_model, db):
    user = django_user_model.objects.create_user("alice", password="pass")
    api_client.force_authenticate(user=user)
    return api_client

@pytest.mark.django_db
def test_create(auth_client):
    resp = auth_client.post("/api/items/", {"name": "Widget"}, format="json")
    assert resp.status_code == status.HTTP_201_CREATED
    assert resp.data["name"] == "Widget"

@pytest.mark.django_db
def test_token_auth(api_client, django_user_model):
    from rest_framework.authtoken.models import Token
    user = django_user_model.objects.create_user("alice", password="pass")
    token = Token.objects.create(user=user)
    api_client.credentials(HTTP_AUTHORIZATION="Token " + token.key)
    resp = api_client.get("/api/me/")
    assert resp.status_code == status.HTTP_200_OK
python
import pytest
from rest_framework.test import APIClient
from rest_framework import status

@pytest.fixture
def api_client():
    return APIClient()

@pytest.fixture
def auth_client(api_client, django_user_model, db):
    user = django_user_model.objects.create_user("alice", password="pass")
    api_client.force_authenticate(user=user)
    return api_client

@pytest.mark.django_db
def test_create(auth_client):
    resp = auth_client.post("/api/items/", {"name": "Widget"}, format="json")
    assert resp.status_code == status.HTTP_201_CREATED
    assert resp.data["name"] == "Widget"

@pytest.mark.django_db
def test_token_auth(api_client, django_user_model):
    from rest_framework.authtoken.models import Token
    user = django_user_model.objects.create_user("alice", password="pass")
    token = Token.objects.create(user=user)
    api_client.credentials(HTTP_AUTHORIZATION="Token " + token.key)
    resp = api_client.get("/api/me/")
    assert resp.status_code == status.HTTP_200_OK

factory_boy Integration

factory_boy集成

python
undefined
python
undefined

factories.py

factories.py

from factory.django import DjangoModelFactory import factory
class UserFactory(DjangoModelFactory): class Meta: model = "auth.User" django_get_or_create = ("username",)
username = factory.Sequence(lambda n: f"user{n}")
email    = factory.LazyAttribute(lambda o: f"{o.username}@example.com")
password = factory.django.Password("testpass123")
class ArticleFactory(DjangoModelFactory): class Meta: model = "myapp.Article"
title  = factory.Sequence(lambda n: f"Article {n}")
author = factory.SubFactory(UserFactory)
body   = factory.Faker("paragraph")
from factory.django import DjangoModelFactory import factory
class UserFactory(DjangoModelFactory): class Meta: model = "auth.User" django_get_or_create = ("username",)
username = factory.Sequence(lambda n: f"user{n}")
email    = factory.LazyAttribute(lambda o: f"{o.username}@example.com")
password = factory.django.Password("testpass123")
class ArticleFactory(DjangoModelFactory): class Meta: model = "myapp.Article"
title  = factory.Sequence(lambda n: f"Article {n}")
author = factory.SubFactory(UserFactory)
body   = factory.Faker("paragraph")

conftest.py

conftest.py

@pytest.fixture def user(db): return UserFactory()
@pytest.fixture def article(db): return ArticleFactory()
undefined
@pytest.fixture def user(db): return UserFactory()
@pytest.fixture def article(db): return ArticleFactory()
undefined

Testing Signals

测试信号

python
from unittest.mock import MagicMock
from django.db.models.signals import post_save

@pytest.mark.django_db
def test_signal_fires(db):
    handler = MagicMock()
    post_save.connect(handler, sender=Article)
    try:
        Article.objects.create(title="Test", body="Content")
        assert handler.called
        assert handler.call_args[1]["created"] is True
    finally:
        post_save.disconnect(handler, sender=Article)
python
from unittest.mock import MagicMock
from django.db.models.signals import post_save

@pytest.mark.django_db
def test_signal_fires(db):
    handler = MagicMock()
    post_save.connect(handler, sender=Article)
    try:
        Article.objects.create(title="Test", body="Content")
        assert handler.called
        assert handler.call_args[1]["created"] is True
    finally:
        post_save.disconnect(handler, sender=Article)

Testing Management Commands

测试管理命令

python
from io import StringIO
from django.core.management import call_command

@pytest.mark.django_db
def test_command_output():
    out = StringIO()
    call_command("my_command", stdout=out, verbosity=2)
    assert "Success" in out.getvalue()
python
from io import StringIO
from django.core.management import call_command

@pytest.mark.django_db
def test_command_output():
    out = StringIO()
    call_command("my_command", stdout=out, verbosity=2)
    assert "Success" in out.getvalue()

Async Views (Django 4.1+)

异步视图(Django 4.1+)

python
@pytest.mark.asyncio
@pytest.mark.django_db(transaction=True)
async def test_async_view(async_client):
    response = await async_client.get("/async-endpoint/")
    assert response.status_code == 200
python
@pytest.mark.asyncio
@pytest.mark.django_db(transaction=True)
async def test_async_view(async_client):
    response = await async_client.get("/async-endpoint/")
    assert response.status_code == 200

Query Count Assertions

查询次数断言

python
def test_no_n_plus_one(client, django_assert_max_num_queries):
    with django_assert_max_num_queries(5):
        response = client.get("/api/articles/")
    assert response.status_code == 200

def test_exact_queries(django_assert_num_queries):
    with django_assert_num_queries(1):
        Article.objects.get(pk=1)
python
def test_no_n_plus_one(client, django_assert_max_num_queries):
    with django_assert_max_num_queries(5):
        response = client.get("/api/articles/")
    assert response.status_code == 200

def test_exact_queries(django_assert_num_queries):
    with django_assert_num_queries(1):
        Article.objects.get(pk=1)

on_commit Callbacks

on_commit回调

python
def test_email_on_commit(client, mailoutbox, django_capture_on_commit_callbacks):
    with django_capture_on_commit_callbacks(execute=True):
        client.post("/orders/", {"item_id": 1})
    assert len(mailoutbox) == 1
python
def test_email_on_commit(client, mailoutbox, django_capture_on_commit_callbacks):
    with django_capture_on_commit_callbacks(execute=True):
        client.post("/orders/", {"item_id": 1})
    assert len(mailoutbox) == 1

Recommended conftest.py

推荐的conftest.py

python
undefined
python
undefined

conftest.py

conftest.py

import pytest
@pytest.fixture(autouse=True) def fast_password_hasher(settings): settings.PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
@pytest.fixture(autouse=True) def dummy_cache(settings): settings.CACHES = {"default": {"BACKEND": "django.core.cache.backends.dummy.DummyCache"}}
@pytest.fixture(autouse=True) def email_backend(settings): settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
undefined
import pytest
@pytest.fixture(autouse=True) def fast_password_hasher(settings): settings.PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
@pytest.fixture(autouse=True) def dummy_cache(settings): settings.CACHES = {"default": {"BACKEND": "django.core.cache.backends.dummy.DummyCache"}}
@pytest.fixture(autouse=True) def email_backend(settings): settings.EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
undefined

Anti-Patterns

反模式

Anti-PatternSolution
transaction=True
for everything
Use default (rollback) unless you need real commits
db
in session fixture
Use
django_db_blocker
for session scope
Not resetting factory sequences
UserFactory.reset_sequence(0)
in fixtures
Not using
force_login
Prefer
force_login
over
login
for speed
反模式解决方案
所有测试都用
transaction=True
除非需要真实提交,否则使用默认的回滚模式
会话级fixture中使用
db
对会话范围使用
django_db_blocker
不重置工厂序列在fixture中调用
UserFactory.reset_sequence(0)
不使用
force_login
优先使用
force_login
以提升速度