Automatisert testing av mikrotjenester i Docker for kontinuerlig integrasjon

I prosjekter knyttet til utvikling av mikrotjenestearkitektur, beveger CI/CD seg fra kategorien en hyggelig mulighet til kategorien en presserende nødvendighet. Automatisert testing er en integrert del av kontinuerlig integrasjon, en kompetent tilnærming til dette kan gi teamet mange hyggelige kvelder med familie og venner. Ellers risikerer prosjektet aldri å bli fullført.

Det er mulig å dekke hele mikroservicekoden med enhetstester med mock-objekter, men dette løser bare delvis problemet og etterlater mange spørsmål og vanskeligheter, spesielt ved testing av arbeid med data. Som alltid er de mest presserende å teste datakonsistens i en relasjonsdatabase, teste arbeid med skytjenester og gjøre uriktige antagelser når du skriver falske objekter.

Alt dette og litt til kan løses ved å teste hele mikrotjenesten i en Docker-beholder. En utvilsom fordel for å sikre gyldigheten av tester er at de samme Docker-bildene som går i produksjon blir testet.

Automatisering av denne tilnærmingen byr på en rekke problemer, løsningen som vil bli beskrevet nedenfor:

  • konflikter av parallelle oppgaver i samme docker-vert;
  • identifikatorkonflikter i databasen under testiterasjoner;
  • venter på at mikrotjenester skal være klare;
  • slå sammen og sende ut logger til eksterne systemer;
  • testing av utgående HTTP-forespørsler;
  • web-socket-testing (ved hjelp av SignalR);
  • testing av OAuth-autentisering og -autorisasjon.

Denne artikkelen er basert på min tale på SECR 2019. Så for de som er for late til å lese, her er et opptak av talen.

Automatisert testing av mikrotjenester i Docker for kontinuerlig integrasjon

I denne artikkelen vil jeg fortelle deg hvordan du bruker et skript for å kjøre tjenesten som testes, en database og Amazon AWS-tjenester i Docker, deretter tester på Postman og, etter at de er fullført, stopper og sletter de opprettede beholderne. Tester utføres hver gang koden endres. På denne måten sørger vi for at hver versjon fungerer riktig med AWS-databasen og -tjenestene.

Det samme skriptet kjøres både av utviklerne selv på deres Windows-skrivebord og av Gitlab CI-serveren under Linux.

For å være berettiget, bør introduksjon av nye tester ikke kreve installasjon av tilleggsverktøy verken på utviklerens datamaskin eller på serveren der testene kjøres på en commit. Docker løser dette problemet.

Testen må kjøres på en lokal server av følgende årsaker:

  • Nettverket er aldri helt pålitelig. Av tusen forespørsler kan en mislykkes;
    I dette tilfellet vil den automatiske testen ikke fungere, arbeidet vil stoppe, og du må se etter årsaken i loggene;
  • For hyppige forespørsler er ikke tillatt av enkelte tredjepartstjenester.

I tillegg er det uønsket å bruke stativet fordi:

  • Et stativ kan brytes ikke bare av dårlig kode som kjører på det, men også av data som riktig kode ikke kan behandle;
  • Uansett hvor hardt vi prøver å gå tilbake alle endringene som ble gjort av testen under selve testen, kan noe gå galt (hvorfor teste ellers?).

Om prosjekt- og prosessorganisering

Vårt firma utviklet en mikrotjeneste-webapplikasjon som kjører i Docker i Amazon AWS-skyen. Det ble allerede brukt enhetstester på prosjektet, men det oppsto ofte feil som enhetstestene ikke oppdaget. Det var nødvendig å teste en hel mikrotjeneste sammen med databasen og Amazon-tjenester.

Prosjektet bruker en standard kontinuerlig integrasjonsprosess, som inkluderer testing av mikrotjenesten med hver forpliktelse. Etter å ha tildelt en oppgave, gjør utvikleren endringer i mikrotjenesten, tester den manuelt og kjører alle tilgjengelige automatiserte tester. Om nødvendig endrer utvikleren testene. Hvis ingen problemer blir funnet, forpliktes det til grenen av dette problemet. Etter hver commit kjøres tester automatisk på serveren. Sammenslåing til en felles gren og lansering av automatiske tester på den skjer etter en vellykket gjennomgang. Hvis testene på den delte grenen består, oppdateres tjenesten automatisk i testmiljøet på Amazon Elastic Container Service (benk). Stativet er nødvendig for alle utviklere og testere, og det er ikke tilrådelig å bryte det. Testere i dette miljøet sjekker en rettelse eller en ny funksjon ved å utføre manuelle tester.

Prosjektarkitektur

Automatisert testing av mikrotjenester i Docker for kontinuerlig integrasjon

Applikasjonen består av mer enn ti tjenester. Noen av dem er skrevet i .NET Core og noen i NodeJs. Hver tjeneste kjører i en Docker-beholder i Amazon Elastic Container Service. Hver har sin egen Postgres-database, og noen har også Redis. Det er ingen vanlige databaser. Hvis flere tjenester trenger samme data, blir disse dataene, når de endres, overført til hver av disse tjenestene via SNS (Simple Notification Service) og SQS (Amazon Simple Queue Service), og tjenestene lagrer dem i sine egne separate databaser.

SQS og SNS

SQS lar deg sette meldinger i en kø og lese meldinger fra køen ved hjelp av HTTPS-protokollen.

Hvis flere tjenester leser én kø, kommer hver melding bare til én av dem. Dette er nyttig når du kjører flere forekomster av samme tjeneste for å fordele belastningen mellom dem.

Hvis du vil at hver melding skal leveres til flere tjenester, må hver mottaker ha sin egen kø, og SNS er nødvendig for å duplisere meldinger til flere køer.

I SNS oppretter du et emne og abonnerer på det, for eksempel en SQS-kø. Du kan sende meldinger til emnet. I dette tilfellet sendes meldingen til hver kø som abonnerer på dette emnet. SNS har ikke en metode for å lese meldinger. Hvis du under feilsøking eller testing trenger å finne ut hva som sendes til SNS, kan du opprette en SQS-kø, abonnere på ønsket emne og lese køen.

Automatisert testing av mikrotjenester i Docker for kontinuerlig integrasjon

API-gateway

De fleste tjenester er ikke direkte tilgjengelige fra Internett. Tilgang skjer via API Gateway, som sjekker tilgangsrettigheter. Dette er også vår tjeneste, og det finnes tester for det også.

Sanntidsvarsler

Applikasjonen bruker SignalRfor å vise sanntidsvarsler til brukeren. Dette er implementert i varslingstjenesten. Den er tilgjengelig direkte fra Internett og fungerer i seg selv med OAuth, fordi det viste seg å være upraktisk å bygge støtte for Web-sockets inn i Gateway, sammenlignet med å integrere OAuth og varslingstjenesten.

Velkjent testmetode

Enhetstester erstatter ting som databasen med falske objekter. Hvis en mikrotjeneste, for eksempel, prøver å opprette en post i en tabell med en fremmednøkkel, og posten som den nøkkelen refererer til, ikke eksisterer, kan ikke forespørselen utføres. Enhetstester kan ikke oppdage dette.

В artikkel fra Microsoft Det foreslås å bruke en in-memory-database og implementere mock-objekter.

In-memory-databasen er en av DBMS-ene som støttes av Entity Framework. Den ble laget spesielt for testing. Data i en slik database lagres kun til prosessen som bruker den avsluttes. Det krever ikke å lage tabeller og sjekker ikke dataintegriteten.

Mock-objekter modellerer klassen de erstatter bare i den grad testutvikleren forstår hvordan den fungerer.

Hvordan få Postgres til å starte og utføre migreringer automatisk når du kjører en test er ikke spesifisert i Microsoft-artikkelen. Min løsning gjør dette og legger i tillegg ikke til noen kode spesifikt for tester til selve mikrotjenesten.

La oss gå videre til løsningen

Under utviklingsprosessen ble det klart at enhetstester ikke var nok til å finne alle problemer i tide, så det ble besluttet å nærme dette problemet fra en annen vinkel.

Sette opp et testmiljø

Den første oppgaven er å distribuere et testmiljø. Trinn som kreves for å kjøre en mikrotjeneste:

  • Konfigurer tjenesten som testes for det lokale miljøet, spesifiser detaljene for tilkobling til databasen og AWS i miljøvariablene;
  • Start Postgres og utfør migreringen ved å kjøre Liquibase.
    I relasjonelle DBMS-er, før du skriver data inn i databasen, må du lage et dataskjema, med andre ord tabeller. Ved oppdatering av en applikasjon må tabeller bringes til det skjemaet som brukes av den nye versjonen, og helst uten å miste data. Dette kalles migrasjon. Å lage tabeller i en opprinnelig tom database er et spesielt tilfelle av migrering. Migrering kan bygges inn i selve applikasjonen. Både .NET og NodeJS har migreringsrammeverk. I vårt tilfelle er mikrotjenester av sikkerhetsgrunner fratatt retten til å endre dataskjemaet, og migreringen utføres ved hjelp av Liquibase.
  • Start Amazon LocalStack. Dette er en implementering av AWS-tjenester for å kjøre hjemme. Det er et ferdig bilde for LocalStack på Docker Hub.
  • Kjør skriptet for å opprette de nødvendige enhetene i LocalStack. Shell-skript bruker AWS CLI.

Brukes til testing på prosjektet Postbud. Den eksisterte før, men den ble lansert manuelt og testet en applikasjon som allerede var utplassert på standen. Dette verktøyet lar deg lage vilkårlige HTTP(S)-forespørsler og sjekke om svarene samsvarer med forventningene. Forespørsler kombineres til en samling, og hele samlingen kan kjøres.

Automatisert testing av mikrotjenester i Docker for kontinuerlig integrasjon

Hvordan fungerer den automatiske testen?

Under testen fungerer alt i Docker: tjenesten som testes, Postgres, migreringsverktøyet og Postman, eller rettere sagt konsollversjonen - Newman.

Docker løser en rekke problemer:

  • Uavhengighet fra vertskonfigurasjon;
  • Installere avhengigheter: Docker laster ned bilder fra Docker Hub;
  • Tilbakeføring av systemet til sin opprinnelige tilstand: ganske enkelt å fjerne beholderne.

docker-komponere forener containere til et virtuelt nettverk, isolert fra Internett, der containere finner hverandre etter domenenavn.

Testen styres av et shell-script. For å kjøre testen på Windows bruker vi git-bash. Dermed er ett skript nok for både Windows og Linux. Git og Docker er installert av alle utviklere på prosjektet. Når du installerer Git på Windows, er git-bash installert, så alle har det også.

Skriptet utfører følgende trinn:

  • Byggehavnerbilder
    docker-compose build
  • Lansering av databasen og LocalStack
    docker-compose up -d <контейнер>
  • Databasemigrering og klargjøring av LocalStack
    docker-compose run <контейнер>
  • Lansering av tjenesten som testes
    docker-compose up -d <сервис>
  • Kjører testen (Newman)
  • Stopper alle containere
    docker-compose down
  • Legger ut resultater i Slack
    Vi har en chat hvor meldinger med grønt hake eller rødt kryss og lenke til loggen går.

Følgende Docker-bilder er involvert i disse trinnene:

  • Tjenesten som testes er det samme bildet som for produksjon. Konfigurasjonen for testen er gjennom miljøvariabler.
  • For Postgres, Redis og LocalStack brukes ferdige bilder fra Docker Hub. Det finnes også ferdige bilder for Liquibase og Newman. Vi bygger vårt på skjelettet deres, og legger til filene våre der.
  • For å forberede LocalStack bruker du et ferdig AWS CLI-bilde og lager et bilde som inneholder et skript basert på det.

Hjelp volumer, du trenger ikke å bygge et Docker-bilde bare for å legge til filer i beholderen. Imidlertid er volumer ikke egnet for vårt miljø fordi Gitlab CI-oppgaver i seg selv kjører i containere. Du kan kontrollere Docker fra en slik beholder, men volumer monterer bare mapper fra vertssystemet, og ikke fra en annen beholder.

Problemer du kan støte på

Venter på beredskap

Når en container med en tjeneste kjører, betyr ikke dette at den er klar til å akseptere tilkoblinger. Du må vente til tilkoblingen fortsetter.

Dette problemet løses noen ganger ved hjelp av et skript vente-på-det.sh, som venter på en mulighet til å etablere en TCP-forbindelse. LocalStack kan imidlertid gi en 502 Bad Gateway-feil. I tillegg består den av mange tjenester, og hvis en av dem er klar, sier ikke dette noe om de andre.

beslutning: LocalStack-klargjøringsskript som venter på 200 svar fra både SQS og SNS.

Parallelle oppgavekonflikter

Flere tester kan kjøres samtidig på samme Docker-vert, så container- og nettverksnavn må være unike. Dessuten kan tester fra forskjellige grener av samme tjeneste også kjøres samtidig, så det er ikke nok å skrive navnene deres i hver komponerfil.

beslutning: Skriptet setter COMPOSE_PROJECT_NAME-variabelen til en unik verdi.

Windows-funksjoner

Det er en rekke ting jeg vil påpeke når jeg bruker Docker på Windows, da disse erfaringene er viktige for å forstå hvorfor feil oppstår.

  1. Shell-skript i en beholder må ha Linux-linjeendinger.
    Shell CR-symbolet er en syntaksfeil. Det er vanskelig å si fra feilmeldingen at dette er tilfelle. Når du redigerer slike skript på Windows, trenger du et skikkelig tekstredigeringsprogram. I tillegg må versjonskontrollsystemet konfigureres riktig.

Dette er hvordan git er konfigurert:

git config core.autocrlf input

  1. Git-bash emulerer standard Linux-mapper og, når du kaller en exe-fil (inkludert docker.exe), erstatter absolutte Linux-baner med Windows-baner. Dette gir imidlertid ikke mening for stier som ikke er på den lokale maskinen (eller stier i en container). Denne virkemåten kan ikke deaktiveres.

beslutning: legg til en ekstra skråstrek i begynnelsen av banen: //bin i stedet for /bin. Linux forstår slike baner; for det er flere skråstreker det samme som én. Men git-bash gjenkjenner ikke slike stier og prøver ikke å konvertere dem.

Loggutgang

Når jeg kjører tester, vil jeg gjerne se logger fra både Newman og tjenesten som testes. Siden hendelsene i disse loggene er sammenkoblet, er det mye mer praktisk å kombinere dem i en konsoll enn to separate filer. Newman lanserer via docker-compose kjøring, og utgangen havner derfor i konsollen. Det gjenstår bare å sørge for at utgangen av tjenesten også går dit.

Den opprinnelige løsningen var å gjøre docker-komponere opp ikke noe flagg -d, men ved å bruke shell-funksjonene, send denne prosessen til bakgrunnen:

docker-compose up <service> &

Dette fungerte helt til det var nødvendig å sende logger fra Docker til en tredjepartstjeneste. docker-komponere opp sluttet å sende ut logger til konsollen. Imidlertid jobbet teamet docker vedlegg.

beslutning:

docker attach --no-stdin ${COMPOSE_PROJECT_NAME}_<сервис>_1 &

Identifikatorkonflikt under testiterasjoner

Tester kjøres i flere iterasjoner. Databasen er ikke tømt. Postene i databasen har unike IDer. Hvis vi skriver ned spesifikke IDer i forespørsler, vil vi få en konflikt ved den andre iterasjonen.

For å unngå det, må enten ID-ene være unike, eller alle objekter som er opprettet av testen, må slettes. Noen objekter kan ikke slettes på grunn av krav.

beslutning: generer GUID-er ved hjelp av Postman-skript.

var uuid = require('uuid');
var myid = uuid.v4();
pm.environment.set('myUUID', myid);

Bruk deretter symbolet i spørringen {{myUUID}}, som vil bli erstattet med verdien til variabelen.

Samarbeid via LocalStack

Hvis tjenesten som testes leser eller skriver til en SQS-kø, må selve testen også fungere med denne køen for å verifisere dette.

beslutning: forespørsler fra Postman til LocalStack.

AWS Services API er dokumentert, slik at spørringer kan gjøres uten en SDK.

Hvis en tjeneste skriver til en kø, så leser vi den og sjekker innholdet i meldingen.

Hvis tjenesten sender meldinger til SNS, oppretter LocalStack på forberedelsesstadiet også en kø og abonnerer på dette SNS-emnet. Så kommer alt ned til det som ble beskrevet ovenfor.

Hvis tjenesten trenger å lese en melding fra køen, skriver vi i forrige testtrinn denne meldingen til køen.

Tester HTTP-forespørsler som kommer fra mikrotjenesten som testes

Noen tjenester fungerer over HTTP med noe annet enn AWS, og noen AWS-funksjoner er ikke implementert i LocalStack.

beslutning: i disse tilfellene kan det hjelpe MockServer, som har et ferdig bilde i Docker hub. Forventede forespørsler og svar på dem konfigureres av en HTTP-forespørsel. API-en er dokumentert, så vi gjør forespørsler fra Postman.

Tester OAuth-autentisering og -autorisasjon

Vi bruker OAuth og JSON Web Tokens (JWT). Testen krever en OAuth-leverandør som vi kan kjøre lokalt.

All interaksjon mellom tjenesten og OAuth-leverandøren kommer ned til to forespørsler: først blir konfigurasjonen forespurt /.velkjent/openid-konfigurasjon, og deretter blir den offentlige nøkkelen (JWKS) forespurt på adressen fra konfigurasjonen. Alt dette er statisk innhold.

beslutning: Vår test-OAuth-leverandør er en statisk innholdsserver og to filer på den. Tokenet genereres én gang og forpliktes til Git.

Funksjoner ved SignalR-testing

Postman jobber ikke med websockets. Et spesialverktøy ble laget for å teste SignalR.

En SignalR-klient kan være mer enn bare en nettleser. Det er et klientbibliotek for det under .NET Core. Klienten, skrevet i .NET Core, etablerer en forbindelse, blir autentisert og venter på en bestemt sekvens med meldinger. Hvis en uventet melding mottas eller forbindelsen blir brutt, avsluttes klienten med en kode på 1. Hvis den siste forventede meldingen mottas, avsluttes klienten med en kode på 0.

Newman jobber samtidig med klienten. Flere klienter lanseres for å sjekke at meldinger leveres til alle som trenger dem.

Automatisert testing av mikrotjenester i Docker for kontinuerlig integrasjon

Bruk alternativet for å kjøre flere klienter --skala på docker-compose-kommandolinjen.

Før det kjøres, venter Postman-skriptet på at alle klienter oppretter tilkoblinger.
Vi har allerede støtt på problemet med å vente på en tilkobling. Men det var servere, og her er klienten. En annen tilnærming er nødvendig.

beslutning: klienten i containeren bruker mekanismen Helsesjekkå informere skriptet på verten om statusen. Klienten oppretter en fil på en bestemt bane, si /healthcheck, så snart tilkoblingen er etablert. HealthCheck-skriptet i docker-filen ser slik ut:

HEALTHCHECK --interval=3s CMD if [ ! -e /healthcheck ]; then false; fi

Lag havnearbeider inspisere Viser normal status, helsestatus og utgangskode for beholderen.

Etter at Newman er ferdig, sjekker skriptet at alle beholdere med klienten er avsluttet, med kode 0.

Happinnes finnes

Etter at vi overvant vanskelighetene beskrevet ovenfor, hadde vi et sett med stabile løpetester. I tester fungerer hver tjeneste som en enkelt enhet, og samhandler med databasen og Amazon LocalStack.

Disse testene beskytter et team på 30+ utviklere mot feil i en applikasjon med kompleks interaksjon av 10+ mikrotjenester med hyppige distribusjoner.

Kilde: www.habr.com

Legg til en kommentar