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å
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
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.
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
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.
В
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
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
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
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.
- 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
- 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
Tester OAuth-autentisering og -autorisasjon
Vi bruker OAuth og
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.
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
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