Contract Testing: Defekte API-Versprechen erkennen, bevor sie in Produktion gehen

Du deployst eine Änderung an deinem User-Service. Alle Tests bestehen. Die Pipeline ist grün. Fünf Minuten später wirft der Notification-Service in Produktion Fehler. Benutzer sehen leere Bildschirme. Die Ursache? Du hast ein Feld aus der API-Antwort entfernt, das dein eigener Service nie genutzt hat – aber der Notification-Service war darauf angewiesen.

Integrationstests haben das nicht erkannt. Beide Teams haben ihre eigenen Tests ausgeführt, alles bestanden. Das Problem trat erst auf, als die Services in Produktion tatsächlich miteinander kommuniziert haben. Diese Lücke schließt Contract Testing.

Das eigentliche Problem, das Integrationstests übersehen

Integrationstests prüfen, ob Services sich verbinden und Daten austauschen können. Sie garantieren jedoch nicht, dass die Vereinbarung zwischen den Services gültig bleibt, wenn eine Seite sich ändert. Zwei Teams können unabhängig voneinander perfekt grüne Integrationstests haben und dennoch die Systeme des jeweils anderen beim nächsten Deployment zerstören.

Betrachte es so: Team A betreibt den User-Service. Team B betreibt den Notification-Service, der User-Daten konsumiert. Team A beschließt, die API-Antwort aufzuräumen, indem es ein Feld entfernt, das im eigenen Code ungenutzt aussieht. Ihre Integrationstests bestehen, weil sie gegen ihren eigenen Service testen. Team Bs Integrationstests bestehen ebenfalls, weil sie gegen einen Mock oder einen Snapshot der alten API testen. Der defekte Vertrag zeigt sich erst, wenn beide Services gemeinsam in Staging oder Produktion laufen.

Was Contract Testing tatsächlich tut

Contract Testing macht die implizite Vereinbarung zwischen Services explizit und prüfbar. Jedes Mal, wenn zwei Services über eine API kommunizieren, gibt es eine Abmachung darüber, was gesendet und was empfangen wird. Contract Testing verwandelt diese Abmachung in automatisierte Prüfungen.

Das Konzept arbeitet mit zwei Rollen:

Das folgende Sequenzdiagramm zeigt, wie der Provider einen Vertrag veröffentlicht und der Consumer vor dem Deployment dagegen prüft:

sequenceDiagram participant Provider as User Service (Provider) participant Contract as Contract Repository participant Consumer as Notification Service (Consumer) Provider->>Contract: API-Vertrag veröffentlichen Consumer->>Contract: Vertrag abrufen Consumer->>Consumer: Antwort mit Vertrag abgleichen alt Vertrag gültig Consumer->>Consumer: Sicher deployen else Vertrag defekt Consumer->>Consumer: Build fehlschlagen, Team benachrichtigen end
  • Provider: Der Service, der die API bereitstellt
  • Consumer: Der Service, der die API aufruft

Der Provider erklärt, was er zu liefern garantiert. Der Consumer erklärt, was er tatsächlich benötigt. Solange beide Seiten übereinstimmen, besteht der Contract Test. Wenn sich etwas ändert, bricht der Test, bevor die Änderung in Produktion geht.

Die meisten Teams verwenden einen Consumer-Driven-Ansatz. Der Consumer definiert seine Erwartungen in einer Vertragsdatei. Der Provider prüft dann, ob seine API noch alle Verträge aller Consumer erfüllt. Wenn eine Provider-Änderung einen Vertrag verletzt, erfährt das Provider-Team sofort. Sie können mit dem Consumer-Team sprechen, die Änderung anpassen oder die API versionieren, ohne bestehende Consumer zu beeinträchtigen.

Warum Contract Tests schneller sind als Integrationstests

Der größte praktische Vorteil ist Geschwindigkeit und Unabhängigkeit. Contract Tests benötigen keine anderen Services. Sie brauchen keine echte Datenbank oder eine vollständige Staging-Umgebung. Du führst den Provider einfach mit vordefinierten Testdaten aus und prüfst, ob die Antwort dem Vertrag entspricht.

Ein Contract Test ist in Sekunden abgeschlossen. Ein Integrationstest, der mehrere Services und Datenbanken hochfährt, dauert Minuten. In einer CI-Pipeline macht dieser Unterschied viel aus. Du kannst Contract Tests früh ausführen und schnell scheitern lassen, ohne auf teure Integrations-Suiten warten zu müssen.

Contract Tests helfen auch bei externen Abhängigkeiten, die du nicht kontrollieren kannst. Wenn deine Anwendung eine Drittanbieter-API aufruft, kannst du einen Contract Test schreiben, der prüft, ob die externe API noch das erwartete Format zurückgibt. Wenn der Drittanbieter seine API ändert, schlägt dein Contract Test fehl, und du weißt Bescheid, bevor Benutzer betroffen sind.

Was Contract Tests nicht abdecken

Contract Tests prüfen nur Format und Struktur. Sie verifizieren, dass die richtigen Felder mit den richtigen Typen existieren. Sie prüfen nicht, ob die Daten aus geschäftlicher Sicht korrekt sind. Sie testen keine Netzwerkstabilität, Authentifizierung in Produktion oder Antwortzeiten unter Last.

Für diese Belange benötigst du weiterhin Integrationstests. Contract Tests und Integrationstests ergänzen sich, sie ersetzen einander nicht. Contract Tests erkennen strukturelle Abweichungen früh. Integrationstests erkennen Laufzeit- und Datenprobleme später.

Wo Contract Tests in deine Pipeline passen

Platziere Contract Tests nach Unit-Tests und vor Integrationstests. Diese Reihenfolge ist sinnvoll, weil Contract Tests schneller sind als Integrationstests, aber zusätzliches Vertrauen schaffen, dass die Services, die du gleich gemeinsam testen wirst, noch kompatibel sind. Wenn ein Contract Test fehlschlägt, macht es keinen Sinn, teure Integrationstests auszuführen, die wahrscheinlich ebenfalls fehlschlagen.

Hier ist eine typische Pipeline-Reihenfolge:

  1. Unit-Tests
  2. Contract Tests
  3. Integrationstests
  4. End-to-End-Tests (falls erforderlich)

Wo du anfangen solltest

Versuche nicht, Contract Tests sofort für jeden Service einzuführen. Beginne mit den Services, die sich am häufigsten ändern und bei der Kommunikation mit anderen Services die meisten Probleme verursachen. Das sind die Reibungspunkte, an denen Teams sich gegenseitig häufig behindern.

Achte auf diese Signale:

  • Services, bei denen eine Änderung eines Teams häufig die Funktion eines anderen Teams beeinträchtigt
  • APIs, von denen mehrere Consumer abhängen
  • Services, die ihr Antwortformat regelmäßig ändern
  • Externe APIs, die in der Vergangenheit unerwartet geändert wurden

Konzentriere dich zuerst auf diese. Sobald die Contract Tests laufen und echte Probleme erkennen, erweitere sie schrittweise auf andere Services.

Praktische Checkliste vor der Einführung von Contract Tests

  • Identifiziere die drei wichtigsten Service-Paare, die am häufigsten zu teamübergreifenden Problemen führen
  • Entscheide, ob du Consumer-Driven- oder Provider-Driven-Verträge verwenden möchtest
  • Wähle ein Contract-Testing-Tool, das zu deinem Stack passt (Pact, Spring Cloud Contract oder ähnliches)
  • Beginne mit einem Provider und einem Consumer
  • Führe Contract Tests in CI vor den Integrationstests aus
  • Richte einen Prozess ein, um Teams zu benachrichtigen, wenn ein Vertrag bricht

Das Fazit

Contract Testing erkennt API-Inkompatibilitäten in dem Moment, in dem eine Änderung vorgenommen wird – nicht erst nach dem Deployment. Es läuft schnell, benötigt keine vollständige Umgebung und gibt Teams eine Frühwarnung, bevor defekte Versprechen in Produktion gelangen. Beginne mit deinen schmerzhaftesten Service-Grenzen, automatisiere die Verträge und lass die Pipeline dir sagen, wann die Abmachung bricht. Deine Benutzer werden nie den Unterschied merken – und genau das ist der Punkt.