“Contract-based testen vertelt je of twee applicaties met elkaar kunnen praten zonder gebruik te maken van een testomgeving.”
Provider-driven contract-based testen
In deze benadering schrijft de provider het providercontract. De provider zorgt ervoor dat hun interface-implementatie volledig overeenkomt met het contract. Daarna deelt de provider het contract met hun consumers via de centrale contractrepository. De consumers gebruiken het contract vervolgens als onderdeel van hun tests. Alle consumers gebruiken hetzelfde contract. Dit maakt de benadering ideaal voor populaire en internet-publieke interfaces.
Een providercontract bevat de definities en documentatie van de interface. Het beschrijft alle technische details zoals endpoints en datastructuren. Daarnaast bevat het contract ook details voor mensen, zoals beschrijvingen en voorbeelden. Hierdoor lijkt het schrijven van een providercontract op het schrijven van normale interfacedocumentatie, alleen dan in een standaardformat. Een providercontract kan alleen worden aangepast door de provider. Daarnaast is een providercontract van hoge kwaliteit zo specifiek mogelijk, zodat het geen ruimte laat voor aannames.
De providerkant
De eerste stap voor de provider is het schrijven van het providercontract. De provider gebruikt het contract om feedback op te halen bij de toekomstige consumers en als specificaties voor hun eigen stories. Op basis van het contract bouwt de provider de interface.
Onderdeel van de bouw is testen of de gebouwde interface voldoet aan de specificaties in het contract. Deze stap is cruciaal; hierdoor kunnen de consumers vertrouwen op het contract. De provider moet daarom tijdens deze tests streng zijn voor zichzelf. Elk verschil tussen de gebouwde interface en het contract moet gelijk worden getrokken. De laatste stap voor de provider is het publiceren van het contract naar de centrale contractrepository.
De consumerkant
De consumers wachten totdat het providercontract beschikbaar is op de centrale contractrepository. Dit is het teken dat het contract klaar is voor gebruik. Ze downloaden het contract en gebruiken de informatie daarin om hun kant van de integratie te bouwen. De consumers wijken nooit af van het contract.
Om hun kant van de integratie te testen schrijven de consumers tests. Hierbij gebruiken ze stubs om de provider te simuleren. Alle stubs worden vervolgens gevalideerd met het providercontract. Elk verschil tussen de stubs en het contract moet gelijk worden getrokken. Hierdoor kunnen de stubs nooit grote fouten bevatten.
Het contract wordt elke testrun opnieuw gedownload van de centrale contractrepository. Dit voorkomt dat de stubs achterhaald raken.
Voordelen
De provider-driven benadering is een uitstekende manier om technische correctheid van een integratie te garanderen. Dit klinkt misschien beperkt, maar de praktijk leert dat de meeste tests in deze categorie vallen. Alle tests worden uitgevoerd in isolatie, dit maakt ze aan beide kanten van de integratie snel tijdens het maken en het uitvoeren.
In deze benadering gebruiken alle consumers hetzelfde providercontract. Hierdoor schaalt de benadering uitstekend met het aantal consumers. Er is voor de provider geen verschil tussen 1 en 100 consumers. Dit maakt de benadering ideaal voor populaire en internet-publieke interfaces.
Nadelen
De provider-driven benadering is eenrichtingsverkeer van de provider naar hun consumers. Hierdoor voelt een providercontract als terms and conditions: ‘accept, or get out!’. Dat is vooral vervelend omdat de provider meestal niet echt weet wat de requirements van de consumers zijn. Daarnaast kunnen we geen functionele tests over applicaties heen uitvoeren. Als er voor onze test data tussen applicaties moet vloeien, zal het een end-to-endtest moeten worden.
Consumer-driven contract-based testen
In deze benadering schrijven alle consumers een consumercontract. In de contracten dicteren de consumers de functionaliteit van de interface. De consumers zorgen ervoor dat hun applicaties alle functionaliteiten in hun contract kunnen verwerken. Daarna delen de consumers hun contracten met de provider. De provider gebruikt vervolgens alle contracten om ervoor te zorgen dat hun interface de gedicteerde functionaliteiten kan leveren. Alle consumers schrijven hun eigen contract. Dit maakt de benadering ideaal voor fijn afgestemde interfaces.
Een consumercontract bevat request-responseparen. Deze komen in de vorm van ‘als ik request A stuur, reageer jij met response B’. Op deze manier dicteert het contract hoe de interface eruit moet zien en hoe het zich moet gedragen. Request-responseparen zullen je bekend voorkomen als je wel eens stubs hebt geschreven. Stubs werken ook met request-responseparen. Dit maakt het consumercontract een bron van stubs voor de consumer en daarmee een fundamenteel onderdeel van hun tests. De provider gebruikt de paren als testcases. Een consumercontract kan alleen worden aangepast door de consumer die het heeft geschreven. Een consumercontract van hoge kwaliteit bevat zo weinig mogelijk. Tegelijkertijd bevat het alles wat de consumer nodig heeft van de interface. Dit maakt het een afspiegeling van hoe de interface gebruikt wordt.
De consumerkant
De eerste stap voor elke consumer is het schrijven van een consumercontract. De consumer gebruikt het contract als een specificatiedocument voor de provider en voor hun eigen stories. Sommige onderdelen van het contract zullen onderhandeld moeten worden met de provider.
Wanneer beide partijen het eens zijn, bouwt de consumer hun kant van de integratie op basis van het contract. Vervolgens test de consumer hun kant van de integratie met het contract. Elk verschil tussen de gebouwde integratie en het contract moet gelijk worden getrokken.
De laatste stap voor de consumer is het publiceren van het contract naar de centrale contractrepository.
De providerkant
De provider wacht totdat het consumercontract beschikbaar is op de centrale contractrepository. De provider downloadt alle contracten voor de interface van de centrale contractrepository. Ze gebruiken de contracten als specificaties tijdens het bouwen van de interface. De provider wijkt nooit af van de contracten.
Om de gebouwde interface te testen gebruikt de provider wederom de consumercontracten. Elk contract bevat request-responseparen. De provider gebruikt elk paar als een blackboxtest. Wanneer alle tests slagen, weet de provider zeker dat de gebouwde interface voldoet aan de specificaties van de consumers. Om alle tests te laten slagen zal de provider de bijbehorende testafhankelijkheden moeten maken en onderhouden, zoals data, stubs en andere contracten.
Alle contracten worden elke testrun opnieuw gedownload van de centrale contractrepository. Dit voorkomt dat de provider test met verouderde contracten.
Voordelen
De consumer-driven benadering is een uitstekende manier om technische correctheid van een integratie te garanderen. Daarnaast kan het ook een beetje functionele correctheid garanderen, maar niet iedereen vindt dit een goed idee. Alle tests worden uitgevoerd in isolatie, dit maakt ze aan beide kanten van de integratie snel tijdens het maken en het uitvoeren.
In deze benadering schrijven alle consumers hun eigen consumercontract. Hiermee kunnen de consumers heel duidelijk en precies dicteren wat de interface moet doen. Tegelijkertijd gebruikt de provider de contracten om precies te weten hoe hun interface gebruikt wordt. Op basis van deze feedback kan de provider geïnformeerde beslissingen maken over hun interface.
Nadelen
De consumers hebben veel controle over de interface. Dat kan tot problemen leiden voor de provider. De meeste problemen kunnen alleen worden opgelost door met elkaar te praten. Het grootste probleem dat hierdoor kan ontstaan is dat elke consumer de releasepipeline van de provider kan laten falen. In die situatie kan de provider geen nieuwe code naar productie brengen. De provider moet wachten totdat het foutieve contract aangepast wordt door de verantwoordelijke consumer.
In deze benadering schrijven alle consumers hun eigen contract en de provider moet testafhankelijkheden maken voor elke test in de contracten. Het bijhouden van testafhankelijkheden voor meerdere consumers kan veel werk zijn. Hierdoor neemt de testdruk op de provider toe. Daarnaast kan het testen met meerdere contracten, geschreven door meerdere consumers, zorgen voor dubbele en tegenstrijdige tests. Tot slot is het niet altijd mogelijk om functionele tests over applicaties heen uit te voeren. Waar het wel mogelijk is, is het een potentiële valkuil. Sommigen zeggen dat dit soort functionele testen een antipatroon zijn.
Conclusie
Ik heb een ruwe schets gemaakt van contract-based testen. Het is een krachtige manier om de integratie tussen applicaties te testen. Door onze end-to-endomgeving los te laten kunnen we snellere en minder complexe testen schrijven. Dit kunnen we doen door aan beide kanten van de integratie met dezelfde contracten te testen. Er zijn twee manieren om deze contracten te benaderen. Bij provider-driven contract-based testen schrijft de provider het contract. Deze aanpak schaalt uitstekend met het aantal consumers. Bij consumer-driven contract-based testen schrijven alle consumers een contract. Deze aanpak komt met veel procesvoordelen.