Django includes a testing framework built on the standard library's unittest. It creates and destroys a test database for you and provides a test client that sends requests without a browser. If you prefer pytest, the pytest-django plugin gives you the same capabilities.
Running tests
Put tests in an app's tests.py or in files whose names start with test inside a tests/ package. manage.py test discovers and runs them.
# Everything
python manage.py test
# One app, one class, one method
python manage.py test polls
python manage.py test polls.tests.QuestionModelTests
python manage.py test polls.tests.QuestionModelTests.test_str
# Keep the test database between runs
python manage.py test --keepdb
# Split the run across several processes
python manage.py test --parallel
Django creates a separate database, named with a test_ prefix, and applies your migrations to it. Your real data is never touched.
Testing models with TestCase
django.test.TestCase wraps each test method in a transaction and rolls it back afterwards, so data created in one test never leaks into another. Data shared by several tests can be created once per class in setUpTestData, which keeps the suite fast.
# polls/tests.py
from datetime import timedelta
from django.test import TestCase
from django.utils import timezone
from .models import Question
class QuestionModelTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.question = Question.objects.create(question_text="Favorite language?")
def test_str(self):
self.assertEqual(str(self.question), "Favorite language?")
def test_future_question_is_not_listed(self):
Question.objects.create(
question_text="Future question",
pub_date=timezone.now() + timedelta(days=30),
)
listed = Question.objects.filter(pub_date__lte=timezone.now())
self.assertQuerySetEqual(listed, [self.question])
For pure functions that never touch the database, SimpleTestCase runs faster.
Testing views with the test client
self.client acts like a minimal browser that sends GET and POST requests. The response also exposes the templates and context used to render it.
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse
from .models import Question
class QuestionViewTests(TestCase):
def test_index_lists_questions(self):
Question.objects.create(question_text="First question?")
response = self.client.get(reverse("polls:index"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "First question?")
self.assertTemplateUsed(response, "polls/index.html")
def test_detail_returns_404_for_missing_question(self):
response = self.client.get(reverse("polls:detail", args=[999]))
self.assertEqual(response.status_code, 404)
def test_create_requires_login(self):
url = reverse("polls:create")
response = self.client.post(url, {"question_text": "New question?"})
self.assertEqual(response.status_code, 302)
user = get_user_model().objects.create_user("kim", password="pass1234")
self.client.force_login(user)
response = self.client.post(url, {"question_text": "New question?"})
self.assertEqual(Question.objects.count(), 1)
force_login() logs a user in without checking a password. Build URLs with reverse() rather than hard-coded strings so tests survive URL changes.
Overriding settings and stubbing external calls
Use override_settings to change settings for one test or class, and unittest.mock to replace things that must not really run, such as external API calls.
from unittest import mock
from django.core import mail
from django.test import TestCase, override_settings
@override_settings(EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend")
class NotifyTests(TestCase):
def test_sends_mail(self):
mail.send_mail("Subject", "Body", "from@example.com", ["to@example.com"])
self.assertEqual(len(mail.outbox), 1)
@mock.patch("polls.services.fetch_weather", return_value={"temp": 20})
def test_uses_weather(self, fetch):
from polls.services import summary
self.assertIn("20", summary())
fetch.assert_called_once()
Django uses the in-memory email backend during tests anyway; the override above just makes it explicit.
pytest-django
If you like pytest's plain assert statements and fixtures, install pytest-django.
python -m pip install pytest pytest-django
# pyproject.toml
[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "mysite.settings"
python_files = ["tests.py", "test_*.py"]
# polls/test_views.py
import pytest
from django.urls import reverse
from polls.models import Question
@pytest.mark.django_db
def test_index(client):
Question.objects.create(question_text="A pytest question?")
response = client.get(reverse("polls:index"))
assert response.status_code == 200
assert "A pytest question?" in response.content.decode()
@pytest.mark.django_db
def test_admin_index(admin_client):
response = admin_client.get("/admin/")
assert response.status_code == 200
Tests that touch the database need @pytest.mark.django_db. Fixtures such as client, admin_client, rf (a RequestFactory) and settings are built in, and existing TestCase classes still run. Measure coverage with coverage run -m pytest or pytest-cov.
Summary
python manage.py test runs your suite against a separate test database.TestCase rolls back each test's transaction, and setUpTestData creates shared data once.self.client and reverse() let you check views request by request.- pytest-django offers the same features with
@pytest.mark.django_db and the client fixture, in less code.
コメント 0件
ログイン · ログインするとコメントできます。