Consumer-Driven Contract Testing ist ein Ansatz zum API-Test, bei dem der Consumer einer Schnittstelle festlegt, welche Daten er sendet und welche Antworten er erwartet. Aus diesen Tests entsteht ein maschinenlesbarer Vertrag, gegen den beide Seiten unabhängig voneinander testen. Schlägt ein Test fehl, lässt sich die Software nicht bauen.
Das Wichtigste in Kürze
- Consumer-Driven Contract Testing löst ein strukturelles Problem: Der Consumer definiert selbst, welche Daten und Formate er von einer Schnittstelle erwartet, nicht der Anbieter.
- Der repository-basierte Ansatz mit Open API und Renovate prüft ausschließlich Syntax und Struktur einer Schnittstelle, erkennt aber semantische Änderungen wie das Entfernen einzelner Enum-Werte nicht.
- Pact erzeugt aus Consumer-Tests einen maschinenlesbaren Contract, gegen den beide Seiten unabhängig voneinander testen können, ohne die Gegenstelle live zu benötigen.
- Schlägt ein Contract-Test fehl, blockiert das den Build: Die Software kann nicht released werden, solange der Vertrag gebrochen ist.
Drei Wege, eine API gegen Änderungen abzusichern
Wer eine REST-Schnittstelle anbietet oder konsumiert, hat grob drei Optionen, um Änderungen unter Kontrolle zu halten. Die einfachste ist reines Reden: Zwei Teams setzen sich zusammen, klären den Schnittstellenvertrag mündlich und sind schnell fertig. Aus Tester-Sicht reicht das selten.
Die beiden anderen Wege sind technisch gestützt und lassen sich automatisieren. Der erste ist repository-basiert und versioniert die Schnittstellenbeschreibung selbst. Der zweite ist Contract Testing, bei dem der Konsument seine Erwartungen an die Schnittstelle ausformuliert und prüfbar macht.
Beide Ansätze bauen sinnvollerweise auf Open API auf. Die Schnittstelle wird zuerst mit Open API beschrieben und daraus generiert. Das spart Programmieraufwand und ist weniger fehleranfällig, als die Schnittstelle von Hand zu schreiben.
Wie der repository-basierte Ansatz mit Open API und Renovate funktioniert
Beim repository-basierten Ansatz wird die Open-API-Beschreibung als eigenes, versioniertes Artefakt behandelt. In einem Java-Umfeld landet die generierte Schnittstelle in einem Maven-Repository, das ausschließlich diese Schnittstellenbeschreibung enthält.
Die zweite Komponente ist Renovate. Das Tool liest die Maven-POM-Datei und gleicht die Dependencies darin ab: nicht nur die der Software, sondern auch die der Plugins. Erkennt Renovate eine neue Version, kann es im angebundenen Repository automatisch einen Merge Request erstellen.
Läuft der Build dieses Merge Requests durch, kann Renovate ihn auch automatisch mergen. Schlägt der Build fehl, bleibt der Merge Request für eine manuelle Prüfung liegen. Dann schaut jemand darauf, ob ein Breaking Change vorliegt und was angepasst werden muss.
Der Nutzen liegt darin, dass eine Schnittstellenänderung nicht mehr unbemerkt durchrutscht. Der Konsument bekommt eine neue Version, muss sie übernehmen und stolpert dabei über das, was sich geändert hat. Das funktioniert auch dann, wenn unbekannt ist, wer die Schnittstelle nutzt, weil der Producer die Änderung einfach veröffentlicht.
Warum der repository-Ansatz nur Struktur und Syntax prüft
Renovate erkennt Änderungen an Struktur und Syntax, aber keine semantischen Verschiebungen. Das ist die zentrale Grenze des Ansatzes.
Ein Beispiel macht das deutlich. Ein Parameter ist als String definiert, der Konsument verarbeitet aber faktisch nur zwei zulässige Werte, also eine Art Enum. Streicht der Producer einen dieser Werte, bleibt die Syntax unverändert: Es ist weiterhin ein String. Über den repository-basierten Abgleich fällt diese Änderung deshalb nicht auf.
Hier greift ein Grundsatz aus dem Schnittstellenentwurf. Was eine Schnittstelle ausliefert, sollte möglichst strikt beschrieben sein. Was hereinkommt, sollte sie wohlwollend interpretieren und möglichst viel durchlassen. Solange es um Syntax geht, lässt sich das gut beschreiben. Sobald Semantik im Spiel ist, braucht es eine andere Art von Test.
Consumer-Driven Contract Testing: der Konsument legt fest, was er erwartet
Beim Consumer-Driven Contract Testing beschreibt derjenige, der die Schnittstelle nutzen will, was er von ihr erwartet. Er legt fest, was er schickt und was er als Antwort zurückbekommen muss. Damit lässt sich auch Semantik prüfen.
Andrej Thiele nutzt dafür das Tool Pact mit seinem Builder-Pattern. Über eine DSL beschreibt der Konsument detailliert, dass er etwa einen GET-Request mit bestimmten Daten schickt und Daten in einem definierten Format mit bestimmten Variationen zurückerhält. Für jede Variation, die ihn interessiert, schreibt er einen eigenen Fall.
Das löst das Enum-Problem aus dem repository-Ansatz. Erwartet der Konsument genau zwei Werte, gibt er beide als Beispiel an. Fallen diese Werte plötzlich weg, schlägt der Test sofort an. Der Konsument testet nur das, was er auch tatsächlich auswertet. Werte ihn drei von vielen Feldern interessieren, schreibt er Tests nur für diese drei. Der Rest ist ihm egal.
So läuft der Workflow zwischen Konsument und Producer
Der Ablauf beginnt auf der Konsumentenseite. Dort wird zuerst der Test geschrieben. Aus diesem Test mit seinen Daten entsteht ein Contract, ein Dokument, das entweder im Dateisystem oder auf einem Pact Broker abgelegt wird.
Der Pact Broker lässt sich über HTTPS absichern, sodass nur Beteiligte zugreifen, die die Schnittstelle kennen und den Contract einhalten wollen. Sowohl Konsument als auch Producer lesen denselben Contract aus.
Aus dem Contract erzeugt das Framework eine Art Mock-Server. Auf der Konsumentenseite feuert die Software ihren Request direkt dagegen. Auf der Producer-Seite werden aus dem Contract genau die Daten an den Producer geschickt, die der Konsument angegeben hat, sodass auch der Producer mit realen Erwartungswerten geprüft wird.
Das entkoppelt die Zusammenarbeit. Der Konsument muss nicht auf das fertige Fremdsystem warten, sondern arbeitet schon gegen den Contract. Mehrere Konsumenten können dabei unabhängig voneinander eigene Tests schreiben, die sich inhaltlich nicht überschneiden müssen.
Ohne Reden geht es auch beim Contract Testing nicht
Contract Testing ersetzt die Abstimmung zwischen Teams nicht, es macht sie nur seltener nötig. Üblicherweise wird es so eingestellt, dass die Software gar nicht erst released werden kann, wenn einer der Contract-Tests fehlschlägt.
Genau das erzwingt im Zweifel ein Gespräch. Ändert ein Team etwas und ein anderes zieht nicht nach, sieht der Betroffene zwar, dass etwas fehlschlägt, weiß aber nicht zwangsläufig, was genau passiert ist. Dann muss er mit dem anderen Team reden.
Auch als Informatiker muss man fürchterlich reden. Andrej Thiele
Der harte Vorteil dieses Mechanismus: Niemand kann sich um die Prüfung drücken. Wenn der Test nicht läuft, baut die Software nicht.
Wann sich welcher Ansatz lohnt
Contract-Tests stammen aus dem Microservice-Umfeld. Dort bleiben die Schnittstellen bewusst klein, also entstehen auch nicht massenhaft Tests. In komplexeren Schnittstellen mit fünf oder sechs verschiedenen Konsumenten, die jeweils eigene Tests schreiben, kann die Testbasis dagegen deutlich wachsen.
Die folgende Gegenüberstellung fasst die Unterschiede zusammen:
| Kriterium | Repository-basiert (Open API + Renovate) | Consumer-Driven Contract Testing (Pact) |
|---|---|---|
| Prüft | Struktur und Syntax | Syntax und Semantik |
| Steuerung | Producer-orientiert | Konsument legt Erwartungen fest |
| Erkennt geänderte Enum-Werte | nein | ja |
| Wer kennt die Konsumenten | nicht nötig | bekannte, abgestimmte Konsumenten |
| Einstieg im Betrieb | einfacher | etwas aufwendiger |
Eine gemischte Lösung ist möglich. Man startet etwa mit dem Renovate-Ansatz und ergänzt Contract-Tests dort, wo Semantik wichtig wird. Der Ausbau läuft iterativ: Nicht jeder Konsument muss von Anfang an mitziehen, das Setup wächst Stück für Stück.
Was du für ein Setup brauchst
Der Aufwand hängt vom vorhandenen Technik-Know-how ab. Für einen Demonstrationsaufbau lässt sich die komplette Kette lokal in einer Docker-Umgebung nachbauen, mit eigenem GitLab, einem Nexus in der Community Edition und einem Renovate-Server. Renovate kann dabei auch als GitLab Runner laufen.
Für den Einstieg ist der Pact-Test der leichtere Weg. Einen Pact Broker brauchst du dafür nicht zwingend, der Austausch der Contracts funktioniert auch über das Dateisystem oder ein gemeinsames Verzeichnis, auf das beide Seiten zugreifen. Im laufenden Betrieb ist dafür die Renovate-Variante deutlich einfacher zu handhaben.
Für Integration und Reporting bleibt alles im gewohnten Rahmen. Pact-Tests werden als ganz normale JUnit-Tests geschrieben und laufen als Integrationstests. Der Output kommt aus dem JUnit-Framework und lässt sich in der Pipeline weiterverwenden. Der Pact Broker zeigt zusätzlich an, wenn Contracts gebrochen wurden. Im Kern siehst du das Ergebnis aber direkt an der Software: Sie baut nicht, wenn die Prüfung fehlschlägt.


