Introduksjon til Contract Testing

Hva er Contract Testing og i hvilke situasjoner kan Contract Testing hjelpe oss?

Wessel Braakman

Publisert:

30. mar. 2023

Contract Testing er en metode for programvaretesting som fokuserer på å verifisere samspillet mellom forskjellige komponenter, tjenester eller systemer i en distribuert arkitektur. Denne tilnærmingen er gunstig i scenarier der flere tjenester eller komponenter utvikles og vedlikeholdes av separate team, og det er avgjørende å sikre at de kommuniserer og fungerer sammen korrekt. Kort sagt er Contract Testing en metode for å sikre at to separate systemer (som for eksempel to mikrotjenester) er kompatible og kan kommunisere med hverandre.

Fra en tidligere rolle i et team som utviklet et backend-system med flere grensesnitt, både med front-end og med andre interne og eksterne parter, har jeg mye erfaring med denne typen testing. For å kunne jobbe sammen om samme produkt, men uavhengig av hverandre, opprettet vi disse kontraktene, slik at vi ble enige om hva vi skulle utvikle før vi utviklet det. Dette betydde at begge parter involvert kunne lage mock-ups basert på kontrakten og bygge og teste uavhengig om den andre parten var ferdig med sin utvikling.

Hva er Contract Testing?

Contract simplified.png
Simplified image of an interface contract between two parties

 

I Contract Testing er en kontrakt en formell spesifikasjon av forventet atferd og kommunikasjonsregler mellom to eller flere komponenter, tjenester eller systemer. Den definerer inndata, utdata og samhandlinger for å sikre at de involverte partene oppfyller hverandres forventninger. En kontrakt består vanligvis av følgende elementer:

  • Grensesnitt: Grensesnittet er settet med metoder, funksjoner eller API-endepunkter som er eksponert av en tjeneste eller komponent for at andre tjenester skal kunne bruke det. Det spesifiserer navnene, parametrene og returtypene for disse metodene eller endepunktene.
  • Dataformat: Dataformatet definerer strukturen og typene av data som utveksles mellom tjenestene eller komponentene. For eksempel, i et RESTful API, kan dataformatet være JSON eller XML, og kontrakten vil spesifisere skjemaet for de forventede forespørsels- og responspayloadene.
  • Forutsetninger: Forutsetningene er de vilkårene som må oppfylles før en metode, funksjon eller API-endepunkt kan kalles. Disse forholdene kan omfatte validering av inndata, autentisering eller bestemte systemtilstander som må eksistere før en tjenesteinteraksjon.
  • Etterbetingelser: Etterbetingelser beskriver den forventede tilstanden eller resultatet etter at en metode, funksjon eller API-endepunkt er utført. Dette kan inkludere det forventede responsformatet, statusen for eventuelle opprettede eller endrede data, og eventuelle bivirkninger som skal oppstå som et resultat av interaksjonen.
  • Invarianter: Invarianter er reglene eller forholdene som alltid gjelder gjennom hele samhandlingen mellom komponenter eller tjenester. De definerer begrensninger på systemets oppførsel som må opprettholdes under utførelsen av kontrakten.
  • Feilhåndtering: Kontrakten bør spesifisere hvordan feil eller unntak skal håndteres og kommuniseres mellom de samhandlende tjenestene. Dette inkluderer å definere feilkoder, feilmeldinger og eventuelle forventede gjenopprettingsmekanismer.

Ved å tydelig definere kontrakten i Contract Testing, kan utviklingsteam opprette tester for å verifisere at hver komponent eller tjeneste overholder den spesifiserte atferden og kommunikasjonsreglene. Dette bidrar til å sikre en smidig integrasjon og korrekt fungering av det overordnede systemet.

Når man søker på internett, finner man raskt forkortelsene CDCT og PDCT, som står for Consumer Driven Contract Testing og Provider Driver Contract Testing. CDCT betyr Contract Testing fra synspunktet til grensesnittets forbruker. Den forbrukende parten angir sine behov og forventninger, og leverandøren eller utgiveren må sørge for at disse behovene og forventningene blir oppfylt. I dette tilfellet er forbrukeren i førersetet når det gjelder å definere grensesnittkontrakten. Når PDCT brukes, oppretter leverandøren (ofte kalt utgiver) kontrakten, og forbrukerne må forholde seg til det leverandøren tilbyr. Typen Contract Testing jeg har vært involvert i under min siste oppgave, var en der både forbruker og leverandør kommuniserte mye for å få den beste og mest effektive løsningen for begge parter. Ingen av partene var i ledelsen eller presset gjennom krav.

I hvilke situasjoner kan Contract Testing hjelpe oss?

Tenk deg en stor e-commerce plattform som er utviklet ved hjelp av en mikrotjenestearkitektur. Plattformen består av forskjellige tjenester som brukerstyring, lagerstyring, ordrebehandling, betalingsbehandling og forsendelsessporing, hver administrert av et separat utviklingsteam. Tjenestene kommuniserer med hverandre via API-er og har godt definerte kontrakter som spesifiserer inndata, utdata og oppførsel for hver interaksjon.

I dette scenariet kan Contract Testing være svært gunstig:

  • Sikrer konsistens: Det hjelper med å sikre at hver tjeneste overholder den avtalte kontrakten, noe som garanterer konsistensen i kommunikasjonen mellom tjenestene.
  • Reduserer integrasjonsproblemer: Ved å verifisere at hver tjeneste oppfyller sin kontrakt, kan kontrakts-testing minimere integrasjonsproblemer som kan oppstå på grunn av uoverensstemmende forventninger eller feilaktige implementeringer.
  • Forenkler vedlikehold: Når en tjeneste oppdateres, kan kontrakts-testing oppdage eventuelle utilsiktede endringer i kontrakten, noe som bidrar til å forhindre potensielle problemer i andre avhengige tjenester.
  • Muliggjør parallell utvikling: Siden kontrakter fungerer som en godt definert avtale mellom tjenestene, kan separate team utvikle og teste tjenestene sine uavhengig av hverandre, og stole på kontraktene for integrasjon.
  • Fremmer Continuous Integration and Delivery: Kontraktbasert testing kan inkluderes i CI/CD-pipelinen, noe som gjør det mulig for utviklingsteamene å fange opp og fikse problemer tidlig i utviklingsprosessen, før de når produksjon.

Oppsummert er Contract Testing gunstig i scenarier som mikrotjenestearkitekturer, der flere tjenester eller komponenter samhandler, og det å sikre at disse samhandlingene fungerer som de skal, er avgjørende for påliteligheten og ytelsen til det overordnede systemet. Det er imidlertid ikke begrenset til mikrotjenestearkitekturer. Alle situasjoner der team må opprette eller oppdatere et grensesnitt, kan dra nytte av å lage klare kontrakter. På den måten vet alle involverte parter hva de kan forvente, og endringer som bryter med kontrakten kan knyttes til den avtalte kontrakten.

Eksempel på kontrakt

Tenk at vi har et enkelt Wallet API, med ett endepunkt der vi kan sette inn penger i lommeboken vår. Vårt /deposit-endepunkt har følgende JSON-request body:

{
  "amount": 10.05
}

Parameteren "amount" har en verdi på 10.05 og er derfor et desimaltall med 2 sifret presisjon, så en "double" som det kalles i OpenAPI 3.0-dokumenter.
Vi bestemmer at parameteren "amount" er nødvendig og må være et positivt tall med en minimumsverdi på 0,01. Hvis dette ikke er tilfelle, skal det oppstå en feil med HTTP-kode 400.
Vi bestemmer også at det innskutte beløpet skal øke lommebokens saldo med samme beløp.

/deposit-endepunktet vil svare med en 200 (suksess) kode, og følgende JSON-response body:

{
  "balance": 10.05
}

Parameteren "balance" har også en eksempelverdi på 10,05 og vil derfor også bli definert som en "double" i vår spesifikasjon.
Vi bestemmer at lommebokens "balance" aldri må være et negativt beløp.

Nedenfor finner du et eksempel på en OpenAPI 3.0-spesifikasjon for dette enkle Wallet API-et. Denne API-en har ett enkelt endepunkt (/deposit) som lar en bruker sette inn penger i lommeboken og returnere den oppdaterte lommeboksaldoen. Jeg liker personlig å bruke swagger.io for å modellere kontraktene mine. Den visuelle visningen av OpenAPI 3.0-spesifikasjoner er også veldig nyttig for å gjøre kontrakten lesbar. Mange verktøy (inkludert Confluence) tilbyr plugin-moduler for denne typen dokumentasjon, slik at det kan vises i sin visuelle tilstand, noe som gjør kontrakten mer lesbar for alle involverte parter.

swagger.png
OpenAPI 3.0 contract code of example Wallet API

 

Innenfor denne kontrakten/spesifikasjonen kan vi definere følgende elementer:

  • Grensesnitt: /deposit-endepunktet lar brukere sette inn penger i lommeboken deres.
  • Forutsetning: Beløpsparameteret må oppgis, og det må være et positivt tall (minimumsverdi på 0,01).
  • Dataformat: JSON brukes som dataformat for forespørsels- og responsnyttelast. Forespørselslegemets skjema er Deposit, og responslegemets skjema er Wallet.
  • Postbetingelse: Etter en vellykket innskudd, økes lommeboksaldoen med det innskutte beløpet.
  • Invariant: Lommeboksaldoen skal aldri være negativ (angitt med minimumsverdien 0 i Wallet-skjemaet).
  • Feilhåndtering: Hvis forutsetningen ikke er oppfylt (f.eks. beløpet mangler eller er negativt), returnerer API-et en 400 Bad Request-feil.

Dette enkle Wallet API-et demonstrerer hvordan man inkluderer forutsetninger, dataformater, postbetingelser, invariante betingelser og feilhåndtering i en OpenAPI 3.0-kontrakt. Hvis to team skulle implementere dette API-et (et publiserende team som lager dette API-et, og et konsumerende team som vil bruke dette API-et i applikasjonen deres), kan de nå jobbe separat fordi kontrakten er definert og avtalt. Begge teamene kan individuelt lage simulerte forespørsler og svar basert på denne kontrakten og sørge for at deres del av programvaren fungerer før de faktiske komponentene kobles sammen.

Se nedenfor den mer "lesbare" visningen av denne kontrakten, som genereres av swagger.io-redigeringsprogrammet.

VisualWalletAPI.png
Readable visual interpretation of example Wallet API

 

Hvordan bruker vi kontrakter for Contract Testing?

Hva gjør vi med disse kontraktene når det gjelder testing? En måte å gå fram på er å manuelt sjekke om en forespørsel eller respons overholder den avtalte kontrakten. Men dette er en tidkrevende og kjedelig måte å gjøre det på, og det er ineffektivt å gjøre dette for hånd. Mange verktøy tilbyr nå muligheter for å sjekke en request eller response mot et skjema. Dette finnes både for SOAP og REST-tjenester. I tilleg finnes det onlineverktøy der du kan legge inn en request-/response body og et skjema, der verktøyet validerer om forespørselen/ responsen overholder dette skjemaet. Men det er enda bedre å bruke dette i dine automatiserte tester, for eksempel å legge dem til i Postman-testsettene dine ved å bruke for eksempel Ajv skjema validering.

Når man bruker skjemavalidering, ser vi tre mulige resultater:

a. Både request- og response body overholder den avtalte kontrakten

b. Request body overholder ikke den avtalte kontrakten

c. Response body overholder ikke den avtalte kontrakten

I scenario b, ligger problemet hos forbrukeren av tjenesten. Forbrukeren bør gi en request body som overholder den avtalte kontrakten, fordi dette er det utgivende parten forventer og analyserer for å generere en respons.

I scenario c, ligger problemet hos utgiveren av tjenesten. Responsen er opprettet eller generert av utgiveren av tjenesten og bør alltid overholde den avtalte kontrakten.

I begge tilfeller kan det imidlertid være forvirrende når en feil oppstår med enten forespørselen eller responsen. Siden begge parter bygger programvare og reagerer på hverandre, vil det ikke være klart med en gang hvilken part som har feil. La oss si at en forespørsel blir avvist av API-et fordi request body er feil. Da kan det være at forbrukerparten gjorde en feil ved oppsett av request body, men det kan også være at utgivende part gjorde en feil i den forventede request body. Det samme gjelder for responsen; det kan være en feil fra utgivende part ved å lage en feil respons, men det kan også være en feil fra forbrukerparten ved å tolke den forventede responsen feil. Dette er akkurat hvorfor en kontrakt er så viktig, fordi uten en kontrakt har begge parter like mye si i hva de tenkte eller tolket i form av kode som bør implementeres.

Jeg håper denne bloggen vil hjelpe deg på din QA-reise. I en av mine neste blogger vil jeg fordype meg i hvordan du bruker disse kontraktene i Postman-testene dine, slik at du kan utføre kontraktbasert testing med dette verktøyet!

Artikkel av:

Wessel Braakman
IT-konsulent
Test og Kvalitetssikring