Zum Inhalt springen

Suchen...

Contract-Based Testing

Contract-based Testing entkoppelt Provider und Consumer so, dass Schnittstellenänderungen keine bösen Überraschungen mehr liefern. Wie das konkret funktioniert.

6 Min. Lesezeit
Cover für Contract-Based Testing

Contract-Based Testing bezeichnet eine Methode, bei der zwei Parteien, Provider und Consumer einer Schnittstelle, einen gemeinsamen Kontrakt definieren. Dieser Kontrakt legt alle Interaktionen fest und dient als Mock für Unit-Tests auf beiden Seiten. So lassen sich API-Änderungen früh erkennen, ohne dass beide Systeme gleichzeitig verfügbar sein müssen.

Das Wichtigste in Kürze

  • Contract-based Testing sichert Consumer gegen Breaking Changes ab: Ändert der Provider eine bestehende Schnittstelle, schlagen die Tests fehl, bevor das Problem in der Integration auftaucht.
  • Der Pact Broker hält Contracts immer aktuell, weil beide Seiten gegen dieselbe zentrale Ablage testen und nicht gegen veraltete Dokumentation oder statische Mocks.
  • Contract-Tests prüfen ausschließlich die Kommunikation zwischen Consumer und Provider auf Unit-Test-Ebene, nicht die fachliche Logik hinter der Schnittstelle.
  • Consumer-driven Contracts verhindern unnötige API-Entwicklung: Der Provider implementiert nur, was Consumer konkret anfordern, statt alle denkbaren Schnittstellen bereitzustellen.

Was ist Contract-Based Testing?

Contract-Based Testing prüft die Interaktion zwischen zwei Parteien an einer Schnittstelle, ohne dass beide Systeme zur Testzeit verfügbar sein müssen. Eine Partei stellt die Schnittstelle bereit, die andere nutzt sie. Der Contract ist der Vertrag zwischen beiden und legt fest, welche Interaktionen möglich sind, welche Requests gesendet und welche Responses erwartet werden.

Im Kern ist ein Contract eine Schnittstellenspezifikation, nur lebendiger. Er dokumentiert die Schnittstelle und bleibt aktuell, weil sich Provider und Consumer auf die definierten Interaktionen einigen und diese versioniert ablegen. Die klassische Dokumentation, die nach der ersten Iteration veraltet, fällt damit weg.

Die beiden Rollen sind klar verteilt. Der Provider ist das Backend, das Daten zur Verfügung stellt. Der Consumer ist alles, was diese Daten braucht: andere Produkte, interne oder externe Services, oder ein Frontend, das über Microservices mit dem Backend kommuniziert. Beide arbeiten gegen denselben Contract.

Consumer-driven heißt: nur bauen, was wirklich gebraucht wird

Im Consumer-driven-Ansatz definiert der Consumer, welche Daten und Interaktionen er benötigt, und der Provider implementiert genau das. Das verhindert, dass ein Provider Schnittstellen zu allen denkbaren Daten anbietet, die am Ende niemand abruft.

Dahinter steht das YAGNI-Prinzip, “you aren’t gonna need it”. Statt vorsorglich jede mögliche Schnittstelle zu bauen, entsteht nur, was ein konkreter Consumer angefragt hat. Das Ergebnis ist eine API, die sich an realem Bedarf orientiert.

In der Praxis sieht das so aus: Ein Team stellt seine Contracts auf einem Pact Broker bereit. Andere Teams fragen nach einer Schnittstelle für bestimmte Daten und bekommen den passenden Pact genannt. Sie implementieren dagegen, ohne dass es vorher ein gemeinsames Abstimmungsritual braucht.

Dieser Ansatz macht auch sichtbar, wie Schnittstellen tatsächlich genutzt werden. Wenn niemand einen Contract zieht, kennt das Provider-Team eine ungenutzte Schnittstelle und kann sie hinterfragen.

Wie funktioniert das Testen mit Pact?

Pact ist eines der verbreiteten Frameworks für Contract-Based Testing und gilt als schlank und einfach zu implementieren. Die Tests laufen auf Unit-Test-Ebene: Getestet werden die Klassen und Methoden, die eine API aufrufen, nicht die API selbst.

Die Interaktion wird mit Hilfe des Contracts gemockt. Der eigentliche API-Aufruf findet im Test nicht statt. Das entkoppelt die Systeme voneinander. Du musst nicht warten, bis die Gegenseite fertig implementiert hat oder ein System gerade erreichbar ist, sondern bleibst in deinem eigenen Hoheitsgebiet und kannst regelmäßig testen.

Das funktioniert auch bei einer noch nicht implementierten Schnittstelle. Provider und Consumer einigen sich auf die Interaktionen, und beide nutzen den Contract als Mock. Beide entwickeln gegen denselben Stand und treffen sich in der Mitte, ohne dass eine fertige Implementierung auf der Gegenseite vorausgesetzt wird.

Eine zentrale Schutzfunktion liegt in den Breaking Changes. Ändert der Provider die Schnittstelle, sichert der Contract ab, dass bestehende Interaktionen weiter funktionieren. Neue Interaktionen, die noch kein Consumer nutzt, treffen niemanden. Erst Änderungen an bereits genutzten APIs schlagen im Test an.

Der Pact Broker hält den Stand aktuell

Der Pact Broker ist der Ort, an dem Contracts abgelegt und versioniert werden. Provider und Consumer laden ihre Pacts dort hoch und ziehen sie von dort. So testen alle Beteiligten gegen denselben, aktuellen Stand.

Bei der Testausführung lassen sich die Pacts direkt vom Broker laden. Damit testest du gegen die aktuelle Implementierung, nicht gegen eine alte Kopie. Genau das macht den Broker zur empfohlenen Wahl gegenüber selbstgebauten Ablagen auf eigenen Servern oder in einem Repository.

Die Tests gehören in die CI-Pipeline. Dort zeigt sich sofort, ob eine Schnittstellenänderung Probleme verursacht. Ändert sich die Schnittstelle, schlagen die Integrationsdaten fehl, und das Team erfährt es früh. Pact lässt sich auch in Containerumgebungen einbinden, dafür gibt es ein Docker-Image.

Warum du der Versuchung widerstehen solltest, zu viel zu testen

Der häufigste Fallstrick ist, zu viel testen zu wollen. Contract-Based Testing prüft die Kommunikation, nicht die fachliche Logik hinter der Schnittstelle. Wer versucht, funktionale Tests des Providers mit Pact abzubilden, missbraucht das Werkzeug.

Konkret geht es um die Frage, ob eine Interaktion funktioniert. Beim Anlegen eines neuen Artikels in einem Shop prüfst du, ob die Schnittstelle einen Erfolg liefert oder ob ein Fehler auftaucht, etwa weil ein Pflichtfeld fehlt. Warum eine Validierung im Provider fehlschlägt, gehört eine Ebene höher und interessiert den Contract-Test nicht. Hauptsache, die API liefert den passenden Fehler zurück.

Auch bei den Daten lohnt Zurückhaltung. Geprüft werden Datentypen, nicht konkrete Werte. Erwartet wird ein String, nicht ein bestimmter Wunsch-String. Das hält die Tests flexibel und verhindert flaky Tests, die bei jeder Datenänderung kippen.

Diese Beschränkung verlangt ein Umdenken. Bei Schnittstellen ist man schnell verleitet, das andere System gleich mitzutesten. Genau das fällt hier weg. Der Test bleibt auf die Interaktion fokussiert, und mehr soll er nicht leisten.

Wann Contract-Based Testing nicht das richtige Werkzeug ist

Contract-Based Testing braucht zwei Parteien, die sich auf einen Vertrag einlassen. Fehlt eine Seite oder spielt sie nicht mit, kommt kein sinnvoller Contract zustande. Mehrere Szenarien sprechen gegen den Ansatz:

  • Public APIs: Hier fehlt die Consumer-Seite, die die Entwicklung treibt. Der Provider existiert, aber die Schnittstelle wird nicht vom Consumer gestaltet. Kein guter Fall für Contract Testing.
  • Performance- und Lasttests: Pact ist dafür nicht gedacht. Auch wenn API-Aufrufe simuliert werden, gehören solche Tests in andere Werkzeuge.
  • Fehlende Datenkontrolle: Wenn sich die bereitgestellten Daten unabhängig im Hintergrund ändern können, bilden die Interaktionen nicht mehr den realen Stand ab.
  • Fehlende Bereitschaft der Gegenseite: Will die andere Partei Contract-Based Testing aus organisatorischen oder politischen Gründen nicht nutzen, trefft ihr euch nicht in der Mitte.

Wir wollten die Schnittstelle über Contracts gestalten, und die anderen haben gesagt, nein, wir benutzen das nicht. Obwohl es viele Vorteile hat. Mariusz Smoliński

Die letzte Hürde ist die typischste. Der Contract ist ein Vertrag, und ohne Unterschrift der Gegenseite kommt er nicht zustande. Die Vorteile liegen auf der Hand, doch das allein überzeugt nicht jedes Team.

Wie umfangreich werden Contract-Tests?

Die Anzahl der Tests bleibt in der Regel überschaubar. Wie viele es werden, hängt von der Komplexität der Fälle ab, die eine Schnittstelle lösen soll, und davon, was dem Team wichtig ist.

Als Faustregel reicht es, den Gutfall und die Fehlerfälle abzudecken. Mehr braucht es selten. Ziel ist die minimale Anzahl an Tests, die die Interaktion absichert, gerade weil die Gefahr besteht, zu viel testen zu wollen.

Diese Schlankheit ist kein Kompromiss, sondern Teil des Konzepts. Contract-Tests ersetzen keine End-to-End-Tests und keine funktionalen Provider-Tests. Sie ergänzen sie, indem sie eine Ebene einziehen, auf der die Frage nach Zuständigkeit gar nicht erst entsteht. Beide Seiten testen gegen denselben Contract, unabhängig davon, was das andere System gerade tut.

Diese Seite teilen

Ähnliche Beiträge