Django-Tests: Models, Views und Forms richtig testen
Django bringt ein durchdachtes Test-Framework mit. Der Schlüssel zu einer guten Suite ist, jede Ebene — Models, Forms, Views — mit der passenden Strategie zu testen. Hier meine Vorgehensweise aus vielen Projekten.
TestCase & Datenbank
Djangos TestCase kapselt jeden Test in eine Transaktion, die danach zurückgerollt wird — so bleibt die Test-DB sauber. Für Tests ohne DB-Bedarf nimm das leichtere SimpleTestCase:
from django.test import TestCase, SimpleTestCase
class ReinerLogikTest(SimpleTestCase):
"""Kein DB-Zugriff -> schneller"""
def test_slug(self):
self.assertEqual(slugify("Mein Titel"), "mein-titel")
class MitDatenbankTest(TestCase):
"""Braucht DB -> TestCase"""
def test_erstellung(self):
obj = Article.objects.create(title="Test")
self.assertEqual(Article.objects.count(), 1)Models testen
Bei Models teste ich vor allem: eigene Methoden, Properties, save()-Logik, Constraints und Validierung — nicht die von Django selbst bereitgestellten Felder.
class ArticleModelTest(TestCase):
def test_str(self):
a = Article(title="Hallo Welt")
self.assertEqual(str(a), "Hallo Welt")
def test_publish_setzt_datum(self):
a = Article.objects.create(title="X", status="draft")
a.publish()
self.assertEqual(a.status, "published")
self.assertIsNotNone(a.published_at)
def test_slug_wird_generiert(self):
a = Article.objects.create(title="Mein Artikel")
self.assertEqual(a.slug, "mein-artikel")Forms testen
Forms sind dankbar zu testen: Eingabe rein, is_valid() prüfen, Fehler kontrollieren. Kein Test-Client nötig.
from myapp.forms import ContactForm
class ContactFormTest(TestCase):
def test_gueltig(self):
form = ContactForm(data={
"name": "Eugen", "email": "e@example.com", "message": "Hallo"
})
self.assertTrue(form.is_valid())
def test_email_pflicht(self):
form = ContactForm(data={"name": "Eugen", "message": "Hallo"})
self.assertFalse(form.is_valid())
self.assertIn("email", form.errors)Views mit dem Test-Client
Der Test-Client simuliert HTTP-Requests, ohne einen echten Server zu starten:
class ArticleViewTest(TestCase):
def setUp(self):
self.article = Article.objects.create(title="Test", status="published")
def test_liste_erreichbar(self):
response = self.client.get("/articles/")
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Test")
def test_detail_404_bei_draft(self):
draft = Article.objects.create(title="Entwurf", status="draft")
response = self.client.get(f"/articles/{draft.slug}/")
self.assertEqual(response.status_code, 404)Authentifizierte Views
from django.contrib.auth import get_user_model
class GeschuetzteViewTest(TestCase):
def setUp(self):
User = get_user_model()
self.user = User.objects.create_user("tester", password="geheim123")
def test_redirect_ohne_login(self):
response = self.client.get("/dashboard/")
self.assertEqual(response.status_code, 302) # Redirect zum Login
def test_zugriff_mit_login(self):
self.client.login(username="tester", password="geheim123")
response = self.client.get("/dashboard/")
self.assertEqual(response.status_code, 200)Tests schnell halten
SimpleTestCasewo keine DB nötig istsetUpTestData()stattsetUp()für Daten, die alle Tests teilen (einmal pro Klasse statt pro Test)- Passwort-Hashing im Test beschleunigen: schneller MD5-Hasher in den Test-Settings
# settings/test.py
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
# setUpTestData: einmal pro Testklasse
class ArticleTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.author = User.objects.create_user("autor")Fazit
Jede Django-Ebene hat ihre Teststrategie: Models über ihre Methoden, Forms über Validierung, Views über den Test-Client. Wer SimpleTestCase, setUpTestData und schnelle Hasher nutzt, hält die Suite auch bei hunderten Tests flott. Das ist die Grundlage, auf der sich ein Projekt angstfrei weiterentwickeln lässt.
