Automatiserad testning av mikrotjänster i Docker för kontinuerlig integration

I projekt relaterade till utvecklingen av mikrotjänstarkitektur, flyttar CI/CD från kategorin en trevlig möjlighet till kategorin av en akut nödvändighet. Automatiserad testning är en integrerad del av kontinuerlig integration, ett kompetent förhållningssätt till vilket kan ge teamet många trevliga kvällar med familj och vänner. Annars riskerar projektet att aldrig slutföras.

Det är möjligt att täcka hela mikrotjänstkoden med enhetstester med mock-objekt, men detta löser bara delvis problemet och lämnar många frågor och svårigheter, särskilt när man testar arbete med data. Som alltid är de mest pressande att testa datakonsistens i en relationsdatabas, testa arbete med molntjänster och göra felaktiga antaganden när man skriver skenobjekt.

Allt detta och lite till kan lösas genom att testa hela mikrotjänsten i en Docker-container. En otvivelaktig fördel för att säkerställa testernas giltighet är att samma Docker-bilder som går i produktion testas.

Automatisering av detta tillvägagångssätt ger ett antal problem, vars lösning kommer att beskrivas nedan:

  • konflikter av parallella uppgifter i samma hamnarvärd;
  • identifierarkonflikter i databasen under testiterationer;
  • väntar på att mikrotjänster ska vara redo;
  • slå samman och mata ut loggar till externa system;
  • testa utgående HTTP-förfrågningar;
  • webbsockettestning (med SignalR);
  • testar OAuth-autentisering och auktorisering.

Denna artikel är baserad på mitt tal på SECR 2019. Så för de som är för lata för att läsa, här är en inspelning av talet.

Automatiserad testning av mikrotjänster i Docker för kontinuerlig integration

I den här artikeln kommer jag att berätta hur du använder ett skript för att köra tjänsten som testas, en databas och Amazon AWS-tjänster i Docker, sedan testar på Postman och, efter att de är slutförda, stoppa och ta bort de skapade behållarna. Tester utförs varje gång koden ändras. På så sätt ser vi till att varje version fungerar korrekt med AWS-databasen och tjänsterna.

Samma skript körs både av utvecklarna själva på sina Windows-skrivbord och av Gitlab CI-servern under Linux.

För att vara motiverad bör införandet av nya tester inte kräva installation av ytterligare verktyg varken på utvecklarens dator eller på servern där testerna körs på en commit. Docker löser detta problem.

Testet måste köras på en lokal server av följande skäl:

  • Nätverket är aldrig helt tillförlitligt. Av tusen förfrågningar kan en misslyckas;
    I det här fallet kommer det automatiska testet inte att fungera, arbetet kommer att sluta och du måste leta efter orsaken i loggarna;
  • Alltför frekventa förfrågningar är inte tillåtna av vissa tredjepartstjänster.

Dessutom är det inte önskvärt att använda stativet eftersom:

  • Ett stativ kan brytas inte bara av dålig kod som körs på det, utan också av data som rätt kod inte kan bearbeta;
  • Oavsett hur mycket vi försöker återställa alla ändringar som gjorts av testet under själva testet, kan något gå fel (annars varför testa?).

Om projekt- och processorganisationen

Vårt företag utvecklade en webbapplikation för mikrotjänster som körs i Docker i Amazon AWS-molnet. Enhetstest användes redan i projektet, men det uppstod ofta fel som enhetstesten inte upptäckte. Det var nödvändigt att testa en hel mikrotjänst tillsammans med databasen och Amazon-tjänster.

Projektet använder en standard kontinuerlig integrationsprocess, som inkluderar testning av mikrotjänsten med varje commit. Efter att ha tilldelat en uppgift gör utvecklaren ändringar i mikrotjänsten, testar den manuellt och kör alla tillgängliga automatiserade tester. Vid behov ändrar utvecklaren testerna. Om inga problem hittas görs en commit till grenen av detta nummer. Efter varje commit körs tester automatiskt på servern. Sammanfogning till en gemensam gren och start av automatiska tester på den sker efter en lyckad granskning. Om testen på den delade grenen går igenom uppdateras tjänsten automatiskt i testmiljön på Amazon Elastic Container Service (bänk). Stativet är nödvändigt för alla utvecklare och testare, och det är inte tillrådligt att bryta det. Testare i den här miljön kontrollerar en fix eller en ny funktion genom att utföra manuella tester.

Projektarkitektur

Automatiserad testning av mikrotjänster i Docker för kontinuerlig integration

Applikationen består av mer än tio tjänster. Några av dem är skrivna i .NET Core och några i NodeJs. Varje tjänst körs i en Docker-behållare i Amazon Elastic Container Service. Var och en har sin egen Postgres-databas, och vissa har även Redis. Det finns inga gemensamma databaser. Om flera tjänster behöver samma data, så överförs dessa data, när de ändras, till var och en av dessa tjänster via SNS (Simple Notification Service) och SQS (Amazon Simple Queue Service), och tjänsterna sparar dem i sina egna separata databaser.

SQS och SNS

SQS låter dig lägga meddelanden i en kö och läsa meddelanden från kön med hjälp av HTTPS-protokollet.

Om flera tjänster läser en kö kommer varje meddelande bara till en av dem. Detta är användbart när du kör flera instanser av samma tjänst för att fördela belastningen mellan dem.

Om du vill att varje meddelande ska levereras till flera tjänster måste varje mottagare ha sin egen kö, och SNS behövs för att duplicera meddelanden till flera köer.

I SNS skapar du ett ämne och prenumererar på det, till exempel en SQS-kö. Du kan skicka meddelanden till ämnet. I det här fallet skickas meddelandet till varje kö som prenumererar på detta ämne. SNS har ingen metod för att läsa meddelanden. Om du under felsökning eller testning behöver ta reda på vad som skickas till SNS kan du skapa en SQS-kö, prenumerera på det önskade ämnet och läsa kön.

Automatiserad testning av mikrotjänster i Docker för kontinuerlig integration

API-gateway

De flesta tjänster är inte direkt tillgängliga från Internet. Åtkomst sker via API Gateway, som kontrollerar åtkomsträttigheter. Detta är också vår tjänst, och det finns tester för det också.

Aviseringar i realtid

Applikationen använder SignalRför att visa realtidsmeddelanden för användaren. Detta implementeras i aviseringstjänsten. Den är tillgänglig direkt från Internet och fungerar själv med OAuth, eftersom det visade sig vara opraktiskt att bygga in stöd för webbuttag i Gateway, jämfört med att integrera OAuth och aviseringstjänsten.

Välkänd testmetod

Enhetstest ersätter saker som databasen med skenobjekt. Om en mikrotjänst, till exempel, försöker skapa en post i en tabell med en främmande nyckel, och posten som den nyckeln refererar till inte existerar, kan begäran inte utföras. Enhetstest kan inte upptäcka detta.

В artikel från Microsoft Det föreslås att använda en databas i minnet och implementera skenobjekt.

In-memory-databasen är en av de DBMS som stöds av Entity Framework. Den skapades speciellt för testning. Data i en sådan databas lagras endast tills processen som använder den avslutas. Det kräver inte att skapa tabeller och kontrollerar inte dataintegriteten.

Mock-objekt modellerar klassen de ersätter endast i den mån testutvecklaren förstår hur det fungerar.

Hur du får Postgres att automatiskt starta och utföra migrering när du kör ett test anges inte i Microsoft-artikeln. Min lösning gör detta och lägger dessutom inte till någon kod specifikt för tester till själva mikrotjänsten.

Låt oss gå vidare till lösningen

Under utvecklingsprocessen blev det tydligt att enhetstester inte räckte för att hitta alla problem i tid, så det beslutades att närma sig denna fråga från en annan vinkel.

Att sätta upp en testmiljö

Den första uppgiften är att distribuera en testmiljö. Steg som krävs för att köra en mikrotjänst:

  • Konfigurera tjänsten som testas för den lokala miljön, specificera detaljerna för anslutning till databasen och AWS i miljövariablerna;
  • Starta Postgres och utför migreringen genom att köra Liquibase.
    I relationella DBMS måste du skapa ett dataschema, med andra ord, tabeller innan du skriver data till databasen. Vid uppdatering av en applikation måste tabeller föras till den form som används av den nya versionen, och helst utan att förlora data. Detta kallas migration. Att skapa tabeller i en initialt tom databas är ett specialfall av migrering. Migrering kan byggas in i själva applikationen. Både .NET och NodeJS har migreringsramverk. I vårt fall, av säkerhetsskäl, fråntas mikrotjänster rätten att ändra dataschemat, och migreringen utförs med Liquibase.
  • Starta Amazon LocalStack. Detta är en implementering av AWS-tjänster för att köras hemma. Det finns en färdig bild för LocalStack på Docker Hub.
  • Kör skriptet för att skapa de nödvändiga enheterna i LocalStack. Skalskript använder AWS CLI.

Används för att testa på projektet Postman. Det fanns tidigare, men det lanserades manuellt och testade en applikation som redan var utplacerad i montern. Det här verktyget låter dig göra godtyckliga HTTP(S)-förfrågningar och kontrollera om svaren matchar förväntningarna. Frågor kombineras till en samling och hela samlingen kan köras.

Automatiserad testning av mikrotjänster i Docker för kontinuerlig integration

Hur fungerar det automatiska testet?

Under testet fungerar allt i Docker: tjänsten som testas, Postgres, migreringsverktyget och Postman, eller snarare dess konsolversion - Newman.

Docker löser ett antal problem:

  • Oberoende från värdkonfiguration;
  • Installera beroenden: Docker laddar ner bilder från Docker Hub;
  • Återställa systemet till sitt ursprungliga tillstånd: helt enkelt ta bort behållarna.

docker-komponera förenar behållare till ett virtuellt nätverk, isolerat från Internet, där behållare hittar varandra genom domännamn.

Testet styrs av ett skalskript. För att köra testet på Windows använder vi git-bash. Det räcker alltså med ett skript för både Windows och Linux. Git och Docker installeras av alla utvecklare i projektet. När du installerar Git på Windows är git-bash installerat, så alla har det också.

Skriptet utför följande steg:

  • Bygga hamnarbetare bilder
    docker-compose build
  • Startar databasen och LocalStack
    docker-compose up -d <контейнер>
  • Databasmigrering och förberedelse av LocalStack
    docker-compose run <контейнер>
  • Startar tjänsten som testas
    docker-compose up -d <сервис>
  • Kör testet (Newman)
  • Stoppar alla containrar
    docker-compose down
  • Lägger ut resultat i Slack
    Vi har en chatt där meddelanden med en grön bock eller ett rött kryss och en länk till loggen går.

Följande Docker-bilder är inblandade i dessa steg:

  • Tjänsten som testas är samma bild som för produktion. Konfigurationen för testet är genom miljövariabler.
  • För Postgres, Redis och LocalStack används färdiga bilder från Docker Hub. Det finns även färdiga bilder för Liquibase och Newman. Vi bygger vårt på deras skelett och lägger till våra filer där.
  • För att förbereda LocalStack använder du en färdig AWS CLI-bild och skapar en bild som innehåller ett skript baserat på den.

med användning av volymer, du behöver inte bygga en Docker-bild bara för att lägga till filer i behållaren. Däremot är volymer inte lämpliga för vår miljö eftersom Gitlab CI-uppgifter själva körs i containrar. Du kan styra Docker från en sådan behållare, men volymer monterar bara mappar från värdsystemet och inte från en annan behållare.

Problem du kan stöta på

Väntar på beredskap

När en container med en tjänst körs betyder det inte att den är redo att acceptera anslutningar. Du måste vänta på att anslutningen ska fortsätta.

Det här problemet löses ibland med ett skript vänta-på-det.sh, som väntar på en möjlighet att upprätta en TCP-anslutning. Däremot kan LocalStack skicka ett 502 Bad Gateway-fel. Dessutom består den av många tjänster, och om en av dem är klar säger det inget om de andra.

beslutet: LocalStack provisioneringsskript som väntar på 200 svar från både SQS och SNS.

Parallella uppgiftskonflikter

Flera tester kan köras samtidigt på samma Docker-värd, så container- och nätverksnamn måste vara unika. Dessutom kan tester från olika grenar av samma tjänst också köras samtidigt, så det räcker inte att skriva deras namn i varje skrivfil.

beslutet: Skriptet ställer in variabeln COMPOSE_PROJECT_NAME till ett unikt värde.

Windows-funktioner

Det finns ett antal saker jag vill påpeka när jag använder Docker på Windows, eftersom dessa erfarenheter är viktiga för att förstå varför fel uppstår.

  1. Skalskript i en behållare måste ha Linux-radändelser.
    Skal-CR-symbolen är ett syntaxfel. Det är svårt att av felmeddelandet se att så är fallet. När du redigerar sådana skript på Windows behöver du en ordentlig textredigerare. Dessutom måste versionskontrollsystemet konfigureras korrekt.

Så här är git konfigurerad:

git config core.autocrlf input

  1. Git-bash emulerar vanliga Linux-mappar och, när du anropar en exe-fil (inklusive docker.exe), ersätter absoluta Linux-sökvägar med Windows-sökvägar. Detta är dock inte logiskt för sökvägar som inte finns på den lokala datorn (eller sökvägar i en behållare). Det här beteendet kan inte inaktiveras.

beslutet: lägg till ett extra snedstreck i början av sökvägen: //bin istället för /bin. Linux förstår sådana vägar; för det är flera snedstreck detsamma som en. Men git-bash känner inte igen sådana vägar och försöker inte konvertera dem.

Loggutgång

När jag kör tester skulle jag vilja se loggar från både Newman och tjänsten som testas. Eftersom händelserna i dessa loggar är sammankopplade, är det mycket bekvämare att kombinera dem i en konsol än två separata filer. Newman lanserar via docker-compose körning, och så hamnar dess utdata i konsolen. Allt som återstår är att se till att utdata från tjänsten också går dit.

Den ursprungliga lösningen var att göra docker-komponera upp ingen flagga -d, men med hjälp av skalfunktionerna, skicka denna process till bakgrunden:

docker-compose up <service> &

Detta fungerade tills det var nödvändigt att skicka loggar från Docker till en tredjepartstjänst. docker-komponera upp slutat mata ut loggar till konsolen. Teamet fungerade dock dockningsfäste.

beslutet:

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

Identifieringskonflikt under testiterationer

Tester körs i flera iterationer. Databasen rensas inte. Poster i databasen har unika ID. Om vi ​​skriver ner specifika ID i förfrågningar får vi en konflikt vid den andra iterationen.

För att undvika det måste antingen ID:n vara unika eller så måste alla objekt som skapats av testet tas bort. Vissa objekt kan inte raderas på grund av krav.

beslutet: generera GUID med Postman-skript.

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

Använd sedan symbolen i frågan {{myUUID}}, som kommer att ersättas med variabelns värde.

Samarbete via LocalStack

Om tjänsten som testas läser eller skriver till en SQS-kö, för att verifiera detta måste själva testet också fungera med denna kö.

beslutet: förfrågningar från Postman till LocalStack.

AWS Services API är dokumenterat, vilket gör att frågor kan göras utan SDK.

Om en tjänst skriver till en kö så läser vi den och kontrollerar innehållet i meddelandet.

Om tjänsten skickar meddelanden till SNS skapar LocalStack i förberedelsestadiet också en kö och prenumererar på detta SNS-ämne. Sedan kommer allt till det som beskrevs ovan.

Om tjänsten behöver läsa ett meddelande från kön, så skriver vi i föregående teststeg detta meddelande till kön.

Testar HTTP-förfrågningar som kommer från mikrotjänsten som testas

Vissa tjänster fungerar över HTTP med något annat än AWS, och vissa AWS-funktioner är inte implementerade i LocalStack.

beslutet: i dessa fall kan det hjälpa MockServer, som har en färdig bild i Docker-nav. Förväntade förfrågningar och svar på dem konfigureras av en HTTP-förfrågan. API:t är dokumenterat, så vi gör förfrågningar från Postman.

Testar OAuth-autentisering och auktorisering

Vi använder OAuth och JSON Web Tokens (JWT). Testet kräver en OAuth-leverantör som vi kan köra lokalt.

All interaktion mellan tjänsten och OAuth-leverantören beror på två förfrågningar: för det första begärs konfigurationen /.välkänd/openid-konfiguration, och sedan begärs den publika nyckeln (JWKS) på adressen från konfigurationen. Allt detta är statiskt innehåll.

beslutet: Vår testleverantör av OAuth är en statisk innehållsserver och två filer på den. Token genereras en gång och committeras till Git.

Funktioner i SignalR-testning

Postman arbetar inte med websockets. Ett speciellt verktyg skapades för att testa SignalR.

En SignalR-klient kan vara mer än bara en webbläsare. Det finns ett klientbibliotek för det under .NET Core. Klienten, skriven i .NET Core, upprättar en anslutning, autentiseras och väntar på en specifik sekvens av meddelanden. Om ett oväntat meddelande tas emot eller anslutningen bryts avslutas klienten med koden 1. Om det senast förväntade meddelandet tas emot avslutas klienten med koden 0.

Newman arbetar samtidigt med klienten. Flera klienter lanseras för att kontrollera att meddelanden levereras till alla som behöver dem.

Automatiserad testning av mikrotjänster i Docker för kontinuerlig integration

Använd alternativet för att köra flera klienter --skala på kommandoraden för docker-compose.

Innan Postman-skriptet körs väntar det på att alla klienter upprättar anslutningar.
Vi har redan stött på problemet med att vänta på en anslutning. Men det fanns servrar, och här är klienten. Det behövs ett annat tillvägagångssätt.

beslutet: klienten i behållaren använder mekanismen Hälsokontrollför att informera skriptet på värden om dess status. Klienten skapar en fil på en specifik sökväg, säg /healthcheck, så snart anslutningen har upprättats. HealthCheck-skriptet i docker-filen ser ut så här:

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

Team hamnarbetare inspektera Visar normal status, hälsostatus och utgångskod för behållaren.

När Newman är klar kontrollerar skriptet att alla behållare med klienten har avslutats, med kod 0.

Happinnes finns

Efter att vi övervunnit de ovan beskrivna svårigheterna hade vi en uppsättning stabila löptester. I tester fungerar varje tjänst som en enda enhet och interagerar med databasen och Amazon LocalStack.

Dessa tester skyddar ett team på 30+ utvecklare från fel i en applikation med komplex interaktion av 10+ mikrotjänster med frekventa distributioner.

Källa: will.com

Lägg en kommentar