Een contract is een gesprek, geen schema
Contract testing wordt meestal verkocht als een testtechniek. Je legt het schema van een bericht vast, genereert een mock en laat een pipeline controleren of producer en consumer zich eraan houden.
Dat klopt, maar het mist het belangrijkste. De grootste waarde van een contract zit niet in de geautomatiseerde test. Ze zit in het gesprek dat nodig is om het contract op te stellen.
Het symptoom: de hele keten deployen om één service te testen
Herkenbaar patroon: een team wil zijn consumer-service testen. Daarvoor deployt het de producer mee, en alles wat daartussen zit. Dan start het een flow aan het begin van de keten en kijkt manueel of het juiste event binnenkomt.
Dat is traag, fragiel en afhankelijk van de staat van andere teams. Is de dev-omgeving van de producer net kapot, dan kan de consumer niet testen.
De reflex is om dit technisch op te lossen: betere testomgevingen, meer automatisering. Maar de vraag is waarom het team de producer überhaupt nodig heeft.
De wortel: impliciete afspraken
Het consumerende team deployt de producer omdat het niet expliciet weet wat er kan binnenkomen. Het moet de echte producer zien draaien om het te weten. Het deployen is een symptoom van niet-weten.
Dat patroon staat zelden alleen. Late integratie, testers die niet weten wat ze testen en uitlopende testfases hebben vaak dezelfde oorzaak: afspraken tussen domeinen bestaan in hoofden, niet als vindbaar artefact.
Een typische oorzakenketen: een integratietest loopt uit omdat het schema niet geversioneerd is. Dat komt omdat er nog functionele feedback verwacht wordt. En die komt er niet, omdat het gesprek met de business nog niet heeft plaatsgevonden. De zichtbare pijn ligt vier stappen van de wortel. Meer tooling raakt die wortel niet.
Een contract is breder dan een schema
Een AsyncAPI- of Avro-schema beschrijft de vorm van een bericht. Een volwaardig contract gaat verder: welke flows worden ondersteund, welke use cases zitten erachter, wat betekent elk veld voor de business. Het schema is de formele neerslag van die afspraak, niet de afspraak zelf.
Daarom kan een ontwikkelaar een contract niet alleen opstellen. Er zijn drie rollen nodig:
| Rol | Brengt in |
|---|---|
| Functioneel analist | De flows, use cases en business-betekenis |
| Tester | Wat getest moet worden, en het contract als referentie |
| Ontwikkelaar | De technische vorm, en de vertaling naar code |
Het samenbrengen van die drie is de eigenlijke interventie. De geautomatiseerde test die er later op draait, is grotendeels een bijproduct.
Van gesprek naar test: één gedeeld feature file
Een schema legt de structuur vast. De betekenis leg je vast in gedragsscenario's, in Gherkin. Het scenario beschrijft wat er over de grens tussen twee systemen gebeurt:
Feature: Productieorder afronden
Scenario: Afgewerkte order boekt voorraad af
Given een productieorder voor 500 kg van materiaal X
When het productiesysteem de order als afgewerkt meldt
Then boekt het voorraadsysteem 500 kg van materiaal X af
Dit scenario is de neerslag van het gesprek tussen analist, tester en ontwikkelaar. Het staat in een gedeelde repository, naast het AsyncAPI-contract. Geen van beide teams is eigenaar; het is een gezamenlijke afspraak.
Elk team implementeert alleen zijn eigen helft, met eigen step definitions:
- De producer implementeert Given en When tegen zijn eigen service, en toetst het uitgaande event aan het contract.
- De consumer krijgt het event van een mock die uit het contract gegenereerd is, en implementeert Then tegen zijn eigen service.
- In CI draait elk team zijn helft geïsoleerd. Niemand heeft de service van het andere team nodig.
- Op UAT draait hetzelfde scenario end-to-end over de echte keten. Daar vang je de bedrading en de semantiek die isolatie niet dekt.
Het feature file overleeft technologiewissels. Vervangt een team zijn service, dan herschrijft het alleen zijn step definitions. Het scenario, en dus de afspraak, blijft staan.
Twee soorten flows, twee soorten winst
Wat een contract oplevert, hangt af van de staat van de flow.
| Flow | Aanpak | Winst |
|---|---|---|
| Bestaand en stabiel | Contract afleiden uit het bestaande verkeer, daarna laten bevestigen | Ontkoppeling: de consumer test geïsoleerd tegen een mock |
| Nog in functionele beweging | Contract opstellen terwijl de afspraak ontstaat | Preventie: het ontbreken van een afspraak wordt vroeg zichtbaar |
Beloof die twee niet door elkaar. Bij een stabiele flow werkt een contract meteen. Bij een flow in beweging dwingt het gesprek de afspraak naar voren, maar het maakt die afspraak niet zelf.
Grenzen en valkuilen
Een contract lost niet alles op. Sommige grenzen accepteer je bewust:
- Vorm is geen betekenis. Twee domeinen kunnen hetzelfde schema respecteren en toch iets anders bedoelen met een veld. Dat blijf je pas in een integratie- of acceptatietest zien.
- Bedrading test je op de echte keten. Listener-configuratie, topic-koppeling en serialisatie horen op een gedeelde testomgeving.
Andere risico's kun je beheersen:
- Waargenomen is niet bedoeld. Een contract afgeleid uit bestaand verkeer kan een fout tot norm verheffen. Laat een analist bevestigen dat het contract ook het bedoelde gedrag beschrijft.
- Stille drift. Zonder versionering en compatibiliteitschecks blijven tests groen tegen een verouderd contract. Dat is erger dan geen contract: het geeft vals vertrouwen.
- Het gesprek sterft na de eerste sessie. Dit is het grootste risico, en het is niet technisch. Houd het proces licht en herhaalbaar, en begin met één bewezen voorbeeld.
Afsluiting
Contract testing invoeren als tool is relatief eenvoudig. Het levend houden van de afspraak tussen analist, tester en ontwikkelaar is het moeilijke deel. Dat is waar vergelijkbare initiatieven doorgaans falen, niet op de tooling.
Begin daarom niet bij de tool, maar bij één bestaande integratie en één gesprek. Leg vast wat er werkelijk tussen twee domeinen stroomt en wat het betekent. De mock, de test en de ontkoppeling volgen daarna vanzelf.
Loop je tegen een gelijkaardig probleem aan? Ik denk graag mee.
Neem contact op