← Zurück zum Blog
TestingDjango9. Mai 2026· 12 min Lesezeit

Integrationstests & Testabdeckung (Coverage) messen

Inhalt
  1. Unit vs. Integration vs. E2E
  2. Integrationstests schreiben
  3. Coverage mit coverage.py
  4. Coverage-Reports lesen
  5. Die 100%-Falle
  6. Coverage in CI
  7. Fazit

Unit-Tests prüfen einzelne Bausteine. Aber ob die Bausteine zusammen funktionieren, sagt kein Unit-Test. Dafür braucht es Integrationstests — und um zu wissen, was überhaupt getestet ist, misst man Coverage. Beides mit Augenmaß.

Unit vs. Integration vs. E2E

Die Testpyramide: viele schnelle Unit-Tests unten, weniger Integrationstests in der Mitte, ganz wenige E2E-Tests oben. Wer sie umdreht, bekommt eine langsame, brüchige Suite.

Integrationstests schreiben

In Django ist ein View-Test mit echter DB und echten Services bereits ein Integrationstest — er prüft das Zusammenspiel:

@pytest.mark.django_db
def test_bestellung_anlegen_end_to_end(client, user):
    client.force_login(user)
    # Kompletter Fluss: Request -> View -> Service -> DB
    response = client.post("/bestellung/", {
        "produkt": "SKU-123", "menge": 2,
    })
    assert response.status_code == 302
    # Wirkte sich alles korrekt aus?
    bestellung = Bestellung.objects.get(user=user)
    assert bestellung.positionen.count() == 1
    assert bestellung.gesamtpreis > 0

Hier wird bewusst nicht gemockt — der Wert liegt gerade darin, dass echte Teile zusammenspielen. Externe Dienste (Zahlungsanbieter etc.) mockt man aber weiterhin.

Coverage mit coverage.py

pip install pytest-cov

# Coverage beim Testlauf messen
pytest --cov=myapp --cov-report=term-missing

# HTML-Report (zeigt Zeile für Zeile)
pytest --cov=myapp --cov-report=html
# -> öffne htmlcov/index.html
# .coveragerc - was nicht zählt
[run]
omit =
    */migrations/*
    */tests/*
    */__init__.py
    manage.py
    */settings/*

[report]
exclude_lines =
    pragma: no cover
    raise NotImplementedError
    if __name__ == .__main__.:

Coverage-Reports lesen

Der term-missing-Report zeigt genau, welche Zeilen nie ausgeführt wurden:

Name              Stmts   Miss  Cover   Missing
-----------------------------------------------
myapp/services.py    45      3    93%   28-30
myapp/views.py       60      8    87%   15, 42-48
-----------------------------------------------
TOTAL               105     11    90%

Die Spalte Missing ist das Wertvolle: Zeile 28-30 in services.py ist ungetestet — ist das ein wichtiger Fehlerpfad? Dann Test schreiben. Ein selten genutzter Logging-Zweig? Dann vielleicht nicht.

Die 100%-Falle

100% Coverage klingt gut, ist aber ein Trugschluss: Es sagt nur, dass jede Zeile ausgeführt wurde — nicht, dass sie korrekt ist oder die richtigen Assertions geprüft wurden.

# 100% Coverage, aber nutzloser Test:
def test_gesamtpreis():
    berechne_preis([{"preis": 10}])  # kein assert!
    # Zeile ausgeführt = "covered", aber nichts geprüft
Coverage zeigt, was nicht getestet ist — das ist nützlich. Aber eine hohe Zahl beweist keine Qualität. 80-90% mit sinnvollen Assertions ist mehr wert als 100% ohne.

Coverage in CI durchsetzen

# In der CI-Pipeline: Mindest-Coverage erzwingen
pytest --cov=myapp --cov-fail-under=80
# -> Pipeline schlägt fehl, wenn unter 80%

# Aber: als Untergrenze gegen Verschlechterung,
# nicht als Jagd auf die letzte Zeile.

Fazit

Integrationstests sichern das Zusammenspiel, das Unit-Tests nicht sehen — sparsam eingesetzt nach dem Pyramidenprinzip. Coverage ist ein Kompass, kein Ziel: Sie zeigt ungetestete Pfade, aber die Zahl allein sagt nichts über Qualität. Sinnvolle Assertions bei 85% schlagen leere Tests bei 100%.

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.