Artikel

Trendsz, Tooling

Een van de belangrijkste onderdelen van een goede IT-omgeving is integratie. Het is de lijm die alles bij elkaar houdt en het testen daarvan is complex. Contract-based testen is een manier om integratie tussen applicaties te testen. Het vertelt je of twee applicaties met elkaar kunnen praten zonder gebruik te maken van een testomgeving.

Wat is contract-based testen?

Bij contract-based testen testen we volledige applicaties in isolatie. Hier hebben we geen speciale servers of testomgevingen voor nodig. We kunnen deze tests direct op onze laptop of in een pipeline uitvoeren.

Het is niet alleen een manier van testen, het is vooral een manier van denken. Dit betekent dat contract-based testen zich niet laat limiteren door technische details, zoals welk integratieprotocol er wordt gebruikt. Contract-based testen werkt daarom met de meest bekende protocollen: REST, Kafka en SOAP.

End-to-end hoofdpijn

Het testen van volledige applicaties wordt over het algemeen beschouwd als een taak voor end-to-endtests (ook bekend als ketentests) op een speciale testomgeving. End-to-endomgevingen zijn krachtig. Bijna alle soorten testen die we kunnen bedenken kunnen uitgevoerd worden op een end-to-endomgeving. Helaas gaat end-to-end testen gepaard met een flinke dosis hoofdpijn. Het is fragiel; elke aanpassing op de end-to-endomgeving kan je tests onterecht laten falen. Het is langzaam, vooral tijdens het maken van testen kan het lang duren, wat extra vervelend is als meerdere applicaties synchroon moeten zijn voordat je tests slagen. En onderhoud kost veel tijd, met name omdat er onderdelen van onze testen over de hele omgeving zijn verspreid. Het interessante aan deze hoofdpijnpunten is dat ze allemaal symptomen zijn van tight coupling tijdens het testen. We verstrengelen onze applicaties en teams zo sterk met elkaar dat het onmogelijk wordt om tests effectief te beheren.

Bij contract-based testen couplen we applicaties enkel via contracten. Hierdoor is de coupling veel minder sterk, waardoor we geen hoofdpijn krijgen. Echter, contract-based testen is niet per se een vervanging van end-to-end testen. End-to-end testen is geen slechte manier van testen, maar het is wel een tijdrovende manier die spaarzaam gebruikt moet worden. Ik schat in dat contract-based tests ongeveer 70% tot 100% van bestaande, tijdrovende end-to-end tests volledig kunnen vervangen. Hierdoor blijven er meestal slechts een paar cruciale end-to-end tests over.

Contracten

De naam contract-based testen vertelt ons gelijk dat deze manier van testen sterk afhankelijk is van contracten. Een contract is de documentatie van een applicatie-interface die dient als het gedeelde begrip tussen de interfaceprovider en de consumers. Dit gedeelde begrip is altijd de enige bron van waarheid voor de interface. Het bevat alles wat beide kanten van de integratie nodig hebben om de interface te begrijpen en te gebruiken. Deze simpele principes maken het gedeelde begrip een formeel requirementdocument, maar nog geen contract. Een contract voegt hieraan toe altijd leesbaar te zijn voor zowel mens als machine. Hierdoor kunnen we contracten gebruiken om technische garanties te geven over functionaliteiten die ontworpen zijn voor mensen.

Er zijn twee manieren om contracten te benaderen. Deze benaderingen onderscheiden zich door wie het contract schrijft. Dit heeft grote gevolgen voor wat we er wel en niet mee kunnen doen. Deze benaderingen zijn zo fundamenteel voor contract-based testen dat ze hun eigen naam hebben.

“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.

    Wil je ons nieuwste Paarsz magazine per post ontvangen? Laat dan je gegevens achter.

    Ontwerp zonder titel (19)

    Werken bij Bartosz?

    Vincent Verhelst

    Geïnteresseerd in Bartosz? Dan ga ik graag met jou in gesprek. We kunnen elkaar ontmoeten met een kop koffie bij ons op kantoor. Of tijdens ontbijt, lunch, borrel of diner op een plek die jou het beste uitkomt. Jij mag het zeggen.

    Bijtanken bij Bartosz

    Ketenloos testen. Doe jij al mee?

    Mei28

    De keten voelt vertrouwd, maar eerlijk? De keten zit vaker in de weg dan dat ze ondersteunt.  In een agile omgeving, waar je snel en kort-cyclisch werkt, past een logge keten niet meer. De vraag is dus niet óf je hem loslaat, maar hoe.