V projektoch, ktoré súvisia s vývojom mikroslužbovej architektúry, sa CI/CD posúva z kategórie príjemnej možnosti do kategórie naliehavej potreby. Automatizované testovanie je neoddeliteľnou súčasťou kontinuálnej integrácie a správny prístup k nemu môže tímu poskytnúť množstvo príjemných večerov s rodinou a priateľmi. Inak hrozí riziko, že projekt nebude nikdy dokončený.
Je možné pokryť celý kód mikroslužby jednotkovými testami s mock objektmi, ale to len čiastočne rieši problém a zanecháva množstvo otázok a komplikácií, najmä pri testovaní práce s dátami. Ako vždy, najostrejšie problémy sú testovanie konzistencie dát v relačnej databáze, testovanie práce s cloudovými službami a nesprávne predpoklady pri písaní mock objektov.
Toto všetko a ešte viac sa dá vyriešiť testovaním celej mikroslužby v Docker kontejnery. Nezvratnou výhodou pre zabezpečenie platnosti testov je to, že testy sa vykonávajú na tých istých Docker obrazoch, ktoré idú do produkcie.
Automatizácia takéhoto prístupu predstavuje množstvo problémov, ktorých riešenie bude popísané nižšie:
- konflikty paralelných úloh na jednom Docker hoste;
- konflikty identifikátorov v databáze počas iterácií testu;
- čakanie na pripravenosť mikroslužieb;
- spojenie a výstup logov do externých systémov;
- testovanie odosielaných HTTP požiadaviek;
- testovanie webových socketov (s použitím SignalR);
- testovanie autentifikácie a autorizácie OAuth.
Tento článok je inšpirovaný na SECR 2019. Takže pre tých, ktorí sú leniví čítať, .

V článku opíšem, ako pomocou skriptu spustiť v Dockeri testovanú službu, databázu a služby Amazon AWS, potom testy na Postman a po ich dokončení zastaviť a odstrániť vytvorené kontajnery. Testy sa vykonávajú pri každej zmene kódu. Týmto sa uistíme, že každá verzia správne funguje s databázou a službami AWS.
Ten istý skript spúšťajú ako samotní vývojári na svojich Windows desktopoch, tak aj server Gitlab CI pod Linuxom.
Aby bolo zavedenie nových testov odôvodnené, nemalo by vyžadovať inštaláciu ďalších nástrojov ani na počítači vývojára, ani na serveri, kde sa testy spúšťajú pri commite. Docker túto úlohu rieši.
Test by mal fungovať na lokálnom serveri z nasledujúcich dôvodov:
- Sieť nie je nikdy úplne spoľahlivá. Z tisícky požiadaviek môže jedna zlyhať;
Automatický test v takom prípade nezafunguje, práca sa zastaví a bude potrebné hľadať dôvod v logoch; - Niektoré externé služby neznesú príliš časté požiadavky.
Okrem toho je nežiadúce zapájať stand, pretože:
- Pokaziť stand môže nielen zlý kód, ktorý na ňom beží, ale aj dáta, ktoré správny kód nemôže spracovať;
- Nech sa snažíme akokoľvek vrátiť všetky zmeny vykonané testom, počas samotného testu sa môže niečo pokaziť (inak, na čo by bol test?).
O projekte a organizácii procesu
Naša spoločnosť vyvíjala mikroslužbové webové aplikácie, ktoré bežia v Dockeri v cloude Amazon AWS. Na projekte sa už používali jednotkové testy, avšak často sa objavovali chyby, ktoré jednotkové testy neodhalili. Bolo potrebné testovať celú mikroslužbu spolu s databázou a službami Amazon.
Na projekte sa používa štandardný proces kontinuálnej integrácie, ktorý zahŕňa testovanie mikroslužby pri každom commite. Po priradení úlohy vývojár vykoná zmeny v mikroslužbe, sám ju manuálne testuje a spúšťa všetky existujúce automatizované testy. Ak je to potrebné, vývojár upravuje testy. Ak sa problémy neodhalia, vykoná sa commit do vetvy danej úlohy. Po každom commite sa na serveri automaticky spúšťajú testy. Merge do hlavnej vetvy a spustenie automatizovaných testov na nej prebieha po úspešnom revízii. Ak testy na hlavnej vetve prejdú, služba sa automaticky aktualizuje v testovacom prostredí na Amazon Elastic Container Service (stand). Stand je nevyhnutný pre všetkých vývojárov a testerov a jeho poškodenie je nežiadúce. Testeri v tomto prostredí kontrolujú opravu alebo novú funkciu, vykonávajúc manuálne testy.
Architektúra projektu

Aplikácia pozostáva z viac ako desiatich služieb. Niektoré z nich sú napísané v .NET Core a niektoré v NodeJs. Každá služba funguje v kontajneri Docker v Amazon Elastic Container Service. Každá má svoju databázu Postgres, a niektoré majú aj Redis. Neexistujú žiadne spoločné databázy. Ak viacero služieb potrebuje rovnaké údaje, tieto údaje sa pri ich zmene odovzdávajú každej z týchto služieb prostredníctvom SNS (Simple Notification Service) a SQS (Amazon Simple Queue Service), a služby ich ukladajú do svojich oddelených databáz.
SQS a SNS
SQS umožňuje cez protokol HTTPS pridávať správy do frontu a čítať správy z frontu.
Ak viacero služieb číta jeden front, každá správa príde len jednej z nich. To je užitočné pri spúšťaní viacerých exemplárov jednej služby na rozdelenie záťaže medzi nimi.
Ak je potrebné, aby každá správa bola doručená viacerým službám, každému príjemcovi by mal byť pridelený svoj front, a na duplikáciu správ do viacerých frontov je potrebný SNS.
V SNS vytvoríte tému a prihlásite na ňu napríklad front SQS. Do témy je možné odosielať správy. Pri tom sa správa odošle do každého frontu, ktorý je na túto tému prihlásený. V SNS neexistuje metóda pre čítanie správ. Ak počas ladenia alebo testovania potrebujete zistiť, čo sa odosiela do SNS, môžete vytvoriť front SQS, prihlásiť ho na požadovanú tému a čítať front.

API Gateway
Väčšina služieb nie je priamo dostupná z internetu. Prístup je zabezpečený cez API Gateway, ktorý kontroluje oprávnenia. Toto je tiež naša služba a aj pre ňu sú testy.
Upozornenia v reálnom čase
Aplikácia používa , aby používateľovi zobrazovala upozornenia v reálnom čase. To je implementované v službe upozornení. Je prístupná priamo z internetu a pracuje sama s OAuth, pretože začleniť podporu Web-socketov do Gateway sa ukázalo ako neúčelné v porovnaní s integráciou OAuth a služby upozornení.
Jo poznaný prístup k testovaniu
Jednotkové testy nahrádzajú takými vecami, ako je databáza, s vysoko presnými objektmi. Ak sa mikroslužba pokúša, napríklad, vytvoriť záznam v tabuľke s externým kľúčom a záznam, na ktorý tento kľúč odkazuje, neexistuje, dopyt nemôže byť vykonaný. Jednotkové testy to nemôžu zistiť.
V sa navrhuje použiť in-memory databázu a zavádzať mock-objekty.
In-memory databáza je jednou z databázových systémov, ktoré podporuje Entity Framework. Bola vytvorená špeciálne na testovanie. Dáta v takejto databáze sú uchovávané iba do skončenia procesu, ktorý ju používa. Nie je potrebné vytvárať tabuľky a integrita dát nie je kontrolovaná.
Mock objekty modelujú nahradzovaný triedu len do takej miery, akú testujúci vývojár rozumie jej fungovaniu.
Ako dosiahnuť automatické spustenie Postgres a vykonanie migrácie pri spustení testu, v článku od Microsoftu nie je uvedené. Moje riešenie to robí a navyše sa do samého mikroslužby nepridáva žiadny kód špeciálne pre testy.
Prejdime k riešeniu
Počas vývoja sa ukázalo, že jednotkové testy sú nedostatočné na to, aby sa všetky problémy včas našli, preto sa rozhodlo pristúpiť k tejto otázke iným spôsobom.
Nastavenie testovacieho prostredia
Prvá úloha je nasadiť testovacie prostredie. Kroky potrebné na spustenie mikroslužby:
- Nastaviť testovanú službu na lokálne prostredie, v premenných prostredia sú uvedené údaje na pripojenie k databáze a AWS;
- Spustiť Postgres a vykonať migráciu spustením Liquibase.
V relačných databázových systémoch je pred zapisovaním dát do databázy potrebné vytvoriť schému dát, jednoducho povedané, tabuľky. Pri aktualizácii aplikácie je potrebné tabuľky prispôsobiť novému formátu, pričom je žiaduce, aby sa neztratili dáta. To sa nazýva migrácia. Vytvorenie tabuliek v pôvodne prázdnej databáze je špeciálny prípad migrácie. Migráciu možno integrovať do samotnej aplikácie. V .NET a NodeJS existujú frameworky na migráciu. V našom prípade sú mikroslužby z bezpečnostných dôvodov zbavené práva meniť schému dát a migrácia sa vykonáva pomocou Liquibase. - Spustiť Amazon LocalStack. Toto je implementácia AWS služieb na vlastnom prostredí. Pre LocalStack existuje pripravený obraz v Docker Hub.
- Spustiť skript na vytvorenie potrebných entít v LocalStack. Shell skripty používajú AWS CLI.
Na projekte sa používa na testovanie . Bol tu aj predtým, ale spúšťal sa ručne a testovalo sa aplikácie už nasadené na prostredí. Tento nástroj umožňuje vykonávať ľubovoľné HTTP(S) požiadavky a kontrolovať, či odpovede zodpovedajú očakávaniam. Požiadavky sú zoskupené do kolekcie a je možné spustiť celú kolekciu naraz.

Ako funguje automatizovaný test
Počas testovania v Dockeri funguje všetko: testovaný servis, Postgres, nástroj na migráciu a nástroj Postman, presnejšie jeho konzolová verzia – Newman.
Docker rieši množstvo problémov:
- Nezávislosť od konfigurácie hostiteľa;
- Inštalácia závislostí: Docker sťahuje obrazy z Docker Hubu;
- Návrat systému do pôvodného stavu: jednoducho vymažeme kontajnery.
Docker-compose spája kontajnery do virtuálnej siete, izolovanej od internetu, v ktorej sa kontajnery navzájom nachádzajú podľa doménových mien.
Testom riadi shell skript. Na spustenie testu pod Windows používame git-bash. Takže stačí jediný skript aj pre Windows, aj pre Linux. Git a Docker sú nainštalované u všetkých vývojárov na projekte. Pri inštalácii Git pod Windows sa nainštaluje aj git-bash, takže ho majú tiež všetci.
Skript vykonáva nasledujúce kroky:
- Zostavenie docker obrazov
docker-compose build - Spustenie DB a LocalStacku
docker-compose up -d - Migrácia DB a príprava LocalStacku
docker-compose run - Spustenie testovaného servisu
docker-compose up -d - Spustenie testu (Newman)
- Zastavenie všetkých kontajnerov
docker-compose down - Posielanie výsledkov do Slacku
Máme chat, kam prichádzajú správy so zelenou fajkou alebo červeným krížikom a odkazom na log.
V týchto krokoch sú zapojené nasledujúce Docker obrazy:
- Testovaný servis – ten istý obraz, aký je pre produkciu. Konfigurácia pre test je cez premenné prostredia.
- Pre Postgres, Redis a LocalStack sa používajú hotové obrazy z Docker Hubu. Pre Liquibase a Newman sú tiež hotové obrazy. My budujeme naše na ich základe, pridávame tam naše súbory.
- Na prípravu LocalStacku sa používa hotový obraz AWS CLI, na jeho základe sa vytvára obraz obsahujúci skript.
Použitím , nie je potrebné budovať Docker obraz len na pridanie súborov do kontajnera. Avšak, volumes nie sú vhodné pre naše prostredie, pretože úlohy Gitlab CI bežia v kontajneroch. Z takéhoto kontajnera je možné spravovať Docker, ale volumes montujú priečinky iba z hostiteľskej sústavy, a nie z iného kontajnera.
Problémy, s ktorými sa možno stretnúť
Čakanie na pripravenosť
Keď je kontajner so službou spustený, ešte to neznamená, že je pripravený prijímať pripojenia. Musíme počkať na pripojenie, aby sme mohli pokračovať.
Túto úlohu niekedy riešia pomocou skriptu , ktorý čaká na možnosť vytvorenia TCP pripojenia. Avšak LocalStack môže vrátiť chybu 502 Bad Gateway. Okrem toho sa skladá z viacerých služieb, a ak je jedna z nich pripravená, to nič nehovorí o ostatných.
Riešenie: skripty na prípravu LocalStack, ktoré čakajú na odpoveď 200 od SQS aj od SNS.
Konflikty paralelných úloh
Niekoľko testov môže bežať súčasne na jednom Docker hoste, takže názvy kontajnerov a sietí musia byť jedinečné. Navyše testy z rôznych vetiev jednej služby môžu tiež bežať súčasne, takže nestačí v každom compose súbore uviesť svoje názvy.
Riešenie: skript nastavuje jedinečnú hodnotu pre premennú COMPOSE_PROJECT_NAME.
Zvláštnosti Windows
Pri používaní Dockeru vo Windows je niekoľko vecí, na ktoré by som chcel upozorniť, pretože táto skúsenosť je dôležitá pre pochopenie dôvodov chýb.
- Shell skripty v kontejnere by mali mať linuxové konce riadkov.
Symbol CR pre shell je syntaktická chyba. Po chybovej správe je ťažké pochopiť, že je to týmto problémom. Pri úprave takýchto skriptov vo Windows je potrebný správny textový editor. Okrem toho musí byť systém kontroly verzií správne nastavený.
Takto sa nastavuje git:
git config core.autocrlf input- Git-bash emuluje štandardné priečinky Linuxu a pri vyvolaní exe súboru (vrátane docker.exe) nahrádza absolútne linuxové cesty na Windows cesty. Avšak to nemá význam pre cesty, ktoré nie sú na lokálnom stroji (alebo cesty v kontejnere). Toto správanie sa nedá vypnúť.
Riešenie: pridať ďalší lomítko na začiatok cesty: \/\/bin namiesto \/bin. Linux takéto cesty chápe, preňho je niekoľko lomítok to isté ako jedno. Ale git-bash takéto cesty nerozpoznáva a nepokúša sa ich prevádzať.
Výstup logov
Pri vykonávaní testov by som chcel vidieť logy ako od Newman, tak aj od testovanej služby. Keďže udalosti týchto logov sú navzájom prepojené, kombinovanie ich v jednom konzole je oveľa praktickejšie než dva samostatné súbory. Newman sa spúšťa cez docker-compose run, a preto jeho výstup skončí v konzole. Ostáva zabezpečiť, aby tam bol aj výstup služby.
Pôvodné riešenie spočívalo v tom, aby sa robilo docker-compose up bez praporku -d, ale s využitím funkcií shellu odoslať tento proces do pozadia:
docker-compose up <service> &To fungovalo, pokiaľ nebolo potrebné posielať logy z Dockeru do externého služby. docker-compose up prestalo vypisovať logy do konzoly. Avšak fungoval príkaz docker attach.
Riešenie:
docker attach --no-stdin ${COMPOSE_PROJECT_NAME}__1 &Konflikt identifikátorov pri iteráciách testu
Testy sa spúšťajú niekoľkými iteráciami. Pri tom sa databáza nečistí. Záznamy v databáze majú jedinečné ID. Ak napíšeme konkrétne ID v dopytoch, na druhý iterácii dostaneme konflikt.
Aby sa tomu predišlo, ID musia byť buď jedinečné, alebo je potrebné odstrániť všetky objekty vytvorené testom. Niektoré objekty sa nemôžu odstrániť v súlade s požiadavkami.
Riešenie: generovať GUID-y skriptami v Postman.
var uuid = require('uuid');
var myid = uuid.v4();
pm.environment.set('myUUID', myid);Potom v dopyte použiť symbol {{myUUID}}, ktorý bude nahradený hodnotou premennej.
Interakcia cez LocalStack
Ak testovaná služba číta SQS-queue alebo do nej píše, tak pre overenie toho aj samotný test musí pracovať s touto queue.
Riešenie: dopyty z Postman k LocalStack.
API služieb AWS je zdokumentované, čo umožňuje robiť dopyty bez SDK.
Ak služba píše do queue, čítame ju a kontrolujeme obsah správy.
Ak služba posiela správy do SNS, v štádiu prípravy LocalStack sa vytvára ešte aj queue a predplatí sa na tento SNS-topic. Ďalej všetko prebieha podľa vyššie uvedeného.
Ak služba musí prečítať správu z queue, tak v predchádzajúcom kroku testu píšeme túto správu do queue.
Testovanie HTTP-dopytov, pochádzajúcich od testovaného mikroservisu
Niektoré služby pracujú cez HTTP s niečím iným ako AWS a niektoré funkcie AWS nie sú implementované v LocalStack.
Riešenie: v týchto prípadoch môže pomôcť , ktorý má pripravený obraz v . Očakávané dopyty a odpovede na ne sú nastavované HTTP-dopytom. API je zdokumentované, takže robíme dopyty z Postman.
Testovanie autentifikácie a autorizácie OAuth
Používame OAuth a . Pre test potrebujeme OAuth-provider, ktorý môžeme spustiť lokálne.
Celá interakcia služby s OAuth-providerom spočíva v dvoch dopytoch: najprv sa požaduje konfigurácia /.well-known/openid-configuration, a potom sa požaduje verejný kľúč (JWKS) na adrese z konfigurácie. Všetko toto sú statické obsah.
Riešenie: náš testovací OAuth-provider – to je server statického obsahu a dva súbory na ňom. Token bol vygenerovaný raz a commitnutý do Git.
Špecifiká testovania SignalR
S web socketmi Postman nefunguje. Pre testovanie SignalR bol vytvorený špeciálny nástroj.
Klientom SignalR nemusí byť iba prehliadač. Existuje klientská knižnica pre .NET Core. Klient napísaný v .NET Core nadväzuje spojenie, prechádza autentifikáciou a očakáva určitú sekvenciu správ. Ak je prijatá neočakávaná správa alebo sa spojenie preruší, klient sa ukončí s kódov 1. Po prijatí poslednej očakávanej správy sa ukončí s kódov 0.
S klientom beží aj Newman. Klientov je spustených niekoľko, aby sa overilo, že správy sú doručované všetkým, ktorým majú byť.

Na spustenie viacerých klientov sa používa možnosť --scale v príkazovom riadku docker-compose.
Pred spustením Postman skript čaká na nadviazanie spojenia zo všetkými klientmi.
Problém čakania na spojenie sme už zažili. Ale tam boli servery, a tu je klient. Je potrebný iný prístup.
Riešenie: klient v kontejnere využíva mechanizmus , aby oznámil skriptu na hostiteľovi svoj stav. Klient vytvára súbor na určitom mieste, povedzme, /healthcheck, hneď ako je spojenie nadviazané. HealthCheck skript v docker súbore vyzerá takto:
HEALTHCHECK --interval=3s CMD if [ ! -e /healthcheck ]; then false; fiTím docker inspect ukazuje pre kontajner bežný stav, health stav a kód ukončenia.
Po ukončení Newmana skript kontroluje, že všetky kontajnery s klientom sa ukončili, pričom s kódom 0.
Šťastie je tu
Po tom, čo sme prekonali vyššie uvedené ťažkosti, sme získali súbor stabilne fungujúcich testov. V testoch každý servis funguje ako celok, interaguje s databázou a s Amazon LocalStack.
Tieto testy chránia tím zložený z viac ako 30 vývojárov pred chybami v aplikácii so zložitou interakciou viac ako 10 mikroslužieb pri častých nasadeniach.
Zdroj: habr.com
