← Zurück zum Blog
TestingPython8. Juni 2026· 12 min Lesezeit

Unit-Tests ohne Framework: reine Python-Logik & Services testen

Inhalt
  1. Warum Tests ohne Framework?
  2. Mit unittest
  3. Mit pytest
  4. Service-Layer testen
  5. Logik von Django entkoppeln
  6. Parametrisierte Tests
  7. Fazit

Ein Missverständnis, das mir oft begegnet: dass man in Django-Projekten alles mit dem Django-Testrunner testen müsse. Dabei sind die wertvollsten, schnellsten Tests genau die, die kein Framework brauchen — weil sie reine Logik prüfen.

Warum Tests ohne Framework?

Ein Test, der die Datenbank, das ORM oder den Test-Client hochfährt, ist um Größenordnungen langsamer als einer, der eine Funktion mit Eingabe und erwarteter Ausgabe prüft. Und er ist brüchiger. Deshalb: Kernlogik so schreiben, dass sie ohne Framework testbar ist.

Faustregel: Geschäftslogik gehört nicht in Views oder Models, sondern in reine Funktionen/Services. Dann lässt sie sich blitzschnell testen.

Mit unittest (Standardbibliothek)

# calc.py
def rabatt_berechnen(preis: float, prozent: float) -> float:
    if not 0 <= prozent <= 100:
        raise ValueError("Prozent muss zwischen 0 und 100 liegen")
    return round(preis * (1 - prozent / 100), 2)
# test_calc.py
import unittest
from calc import rabatt_berechnen

class TestRabatt(unittest.TestCase):
    def test_normaler_rabatt(self):
        self.assertEqual(rabatt_berechnen(100, 20), 80.0)

    def test_kein_rabatt(self):
        self.assertEqual(rabatt_berechnen(50, 0), 50.0)

    def test_ungueltig(self):
        with self.assertRaises(ValueError):
            rabatt_berechnen(100, 150)

if __name__ == "__main__":
    unittest.main()

Ausführen mit python -m unittest — keine Abhängigkeiten, läuft überall.

Mit pytest (weniger Boilerplate)

pytest macht dasselbe deutlich knapper — einfache assert-Statements statt assertEqual:

# test_calc.py
import pytest
from calc import rabatt_berechnen

def test_normaler_rabatt():
    assert rabatt_berechnen(100, 20) == 80.0

def test_kein_rabatt():
    assert rabatt_berechnen(50, 0) == 50.0

def test_ungueltig():
    with pytest.raises(ValueError, match="zwischen 0 und 100"):
        rabatt_berechnen(100, 150)

Service-Layer testen

In größeren Projekten kapsle ich Geschäftslogik in Service-Funktionen. Die bekommen ihre Abhängigkeiten übergeben (Dependency Injection) — so sind sie ohne Django testbar:

# services.py
def bestellung_gesamtpreis(positionen: list[dict], versand: float = 0) -> float:
    """Reine Funktion: keine DB, kein ORM, nur Logik."""
    summe = sum(p["preis"] * p["menge"] for p in positionen)
    return round(summe + versand, 2)
def test_gesamtpreis():
    positionen = [
        {"preis": 10.0, "menge": 2},
        {"preis": 5.5, "menge": 1},
    ]
    assert bestellung_gesamtpreis(positionen, versand=4.99) == 30.49

Logik von Django entkoppeln

Der Trick ist Architektur: Statt Logik im Model oder View, eine dünne Schicht drumherum. Das Model hält Daten, der Service rechnet, der View verknüpft:

# Statt: dicke Methode im Model (braucht DB zum Testen)
class Order(models.Model):
    def calculate_total(self): ...  # schwer isoliert testbar

# Besser: reine Funktion, Model ruft sie nur auf
from .services import bestellung_gesamtpreis

class Order(models.Model):
    def calculate_total(self):
        positionen = [{"preis": i.preis, "menge": i.menge} for i in self.items.all()]
        return bestellung_gesamtpreis(positionen, self.versand)

Parametrisierte Tests

Viele Fälle mit einer Testfunktion abdecken — ein pytest-Feature, das ich ständig nutze:

import pytest

@pytest.mark.parametrize("preis,prozent,erwartet", [
    (100, 0, 100.0),
    (100, 50, 50.0),
    (100, 100, 0.0),
    (99.99, 10, 89.99),
])
def test_rabatt_faelle(preis, prozent, erwartet):
    assert rabatt_berechnen(preis, prozent) == erwartet

Fazit

Die schnellsten und stabilsten Tests prüfen reine Logik ohne Framework. Wer Geschäftslogik bewusst von Django entkoppelt, bekommt eine Testsuite, die in Millisekunden läuft und selten grundlos bricht. Framework-Tests (DB, Views) baut man dann gezielt obendrauf — nicht als Ersatz, sondern als Ergänzung.

Yevhen Chubchyk
Yevhen Chubchyk
Senior Python / Django Entwickler · Freelancer seit 2016 · 20+ Jahre IT-Erfahrung. Baut testgetriebene Backend-Systeme für Kunden im DACH-Raum.