A mikroszolgáltatás-architektúrával kapcsolatos projektekben a CI/CD a kellemes lehetőségből éles szükséggé válik. Az automatikus tesztelés elengedhetetlen része a folyamatos integrációnak, amelyhez helyes megközelítés rengeteg kellemes estét biztosíthat a csapatnak a családdal és barátokkal. Ellenkező esetben a projekt veszélybe kerülhet, hogy soha nem fejeződik be.
Az egész mikroszolgáltatás kódját le lehet fedni egységtesztekkel, amelyeken mock-objektumok alapulnak, de ez csak részben oldja meg a problémát, és számos kérdést és nehézséget hagy nyitva, különösen az adatokkal való munka tesztelésekor. Mint mindig, a legélesebb problémák az adatok konzisztenciájának tesztelése a relációs adatbázisban, a felhőszolgáltatásokkal való munka tesztelése, és a mock-objektumok írásakor tett téves feltételezések.
Mindez és még sokkal több megoldható a teljes mikroszolgáltatás Docker-konténerben való tesztelésével. Egyértelmű előny, hogy a tesztek ugyanazokat a Docker-képeket használják, amelyek a termelési környezetbe kerülnek.
E megközelítés automatizálása számos problémát jelent, amelynek megoldását hamarosan leírom:
- konfliktusok párhuzamos feladatok között egy docker-hoszton;
- azonosítókonfliktusok az adatbázisban a teszt iterációk során;
- a mikroszolgáltatások készenléti állapotának várakozása;
- naplók egyesítése és külső rendszerekbe történő kiírása;
- kimenő HTTP-kérelmek tesztelése;
- websocketek tesztelése (SignalR segítségével);
- OAuth azonosítás és jogosultság tesztelése.
Ez a cikk egy a SECR 2019-en. Tehát azoknak, akik lusták olvasni, .

A cikkben elmondom, hogyan indítható el egy tesztelhető szolgáltatás, egy adatbázis és az Amazon AWS szolgáltatások Docker segítségével egy script segítségével, majd a tesztek Postman-en végrehajtása után leállítani és törölni a létrehozott konténereket. A tesztek minden kódbeli változtatáskor futnak. Így biztosítjuk, hogy minden verzió helyesen működik az adatbázissal és az AWS szolgáltatásokkal.
Ugyanaz a script futtatja a fejlesztőket a Windows-asztalaikon és a Linux alatt futó Gitlab CI szervert is.
Ahhoz, hogy az új tesztek bevezetése indokolt legyen, nem szabad további eszközök telepítését igényelnie sem a fejlesztői gépen, sem azon a szerveren, ahol a teszteket futtatják a commit során. A Docker megoldja ezt a feladatot.
A tesztnek a helyi szerveren kell futnia a következő okok miatt:
- A hálózat sosem lehet teljesen megbízható. Ezer kérésből egy esetleg nem megy át;
Automatikus teszt ilyen esetben nem fog működni, a folyamat megáll, és meg kell keresni az okot a naplókban; - Túl gyakori kéréseket egyes külső szolgáltatások nem engednek meg.
Ezen kívül, a stand használata nem javasolt, mert:
- A standot nemcsak a rajta futó hibás kód, hanem olyan adatok is megsérthetik, amelyeket a helyes kód nem tud kezelni;
- Bárhogy is próbálkozunk, hogy visszavonjuk a teszt során végrehajtott összes változtatást, a teszt során valami mindig rosszul sülhet el (máskülönben miért lenne szükség tesztre?).
A projektről és a folyamat szervezéséről
Cégünk egy mikroszolgáltatás alapú webalkalmazást fejlesztett, amely Dockerben működik az Amazon AWS felhőben. A projekt során már használtak egységteszteket, azonban gyakran előfordultak olyan hibák, amelyeket az egységtesztek nem észleltek. Szükséges volt tesztelni az egész mikroszolgáltatást az adatbázissal és az Amazon szolgáltatásaival együtt.
A projekten a folyamatos integráció standard folyamata van alkalmazva, amely tartalmazza a mikroszolgáltatás tesztelését minden egyes commit során. A feladat kijelölése után a fejlesztő módosításokat végez a mikroszolgáltatáson, manuálisan teszteli azt, és futtatja az összes elérhető automatikus tesztet. Ha szükséges, a fejlesztő módosítja a teszteket. Ha problémák nem merülnek fel, akkor commitál a feladat ága alatt. Minden commit után a szerveren automatikusan elindulnak a tesztek. A közös ágba történő merge és az automatikus tesztek futtatása sikeres review után történik. Ha a közös ágon a tesztek sikeresen teljesülnek, a szolgáltatás automatikusan frissül a tesztkörnyezetben az Amazon Elastic Container Service-en (stand). A stand minden fejlesztő és tesztelő számára szükséges, így nem szabad tönkretenni. A tesztelők ezen a környezeten ellenőrzik a javítást vagy az új funkciót, manuális teszteket végezve.
A projekt architektúrája

Az alkalmazás több mint tíz szolgáltatásból áll. Néhány .NET Core-on, míg mások NodeJs-en vannak írva. Minden szolgáltatás Docker-konténerben fut az Amazon Elastic Container Service-ben. Mindegyiknek saját Postgres adatbázisa van, néhányuknak pedig Redis is. Nincs közös adatbázis. Ha több szolgáltatásnak szüksége van azonos adatokra, akkor ezek az adatok a módosításuk pillanatában SNS (Simple Notification Service) és SQS (Amazon Simple Queue Service) révén kerülnek átadásra minden egyes szolgáltatásnak, és a szolgáltatások eltárolják őket saját elkülönített adatbázisaikban.
SQS és SNS
Az SQS lehetővé teszi, hogy HTTPS protokollon keresztül üzeneteket helyezzen el egy sorban és olvasson üzeneteket a sorból.
Ha több szolgáltatás olvassa egy sor tartalmát, akkor minden üzenet csak egynek a szolgáltatásnak jut el. Ez hasznos, ha több példányt indítunk el egy szolgáltatásból, hogy elosszuk a terhelést közöttük.
Ha szükséges, hogy minden üzenet eljusson több szolgáltatáshoz, akkor minden címzettnek saját sorra van szüksége, és az üzenetek több sorba való másolásához SNS szükséges.
Az SNS-ben topicot hoz létre, és például egy SQS-sort feliratkoztat rá. A topicra üzeneteket lehet küldeni, így az üzenet minden, a topicra feliratkozott sorba eljut. Az SNS-ben nincs üzenetek olvasására szolgáló metódus. Ha hibakeresés vagy tesztelés során szükséges tudni, hogy mi kerül az SNS-be, akkor létrehozhat egy SQS-sort, amelyet a szükséges topicra feliratkoztat, és olvashatja a sort.

API Gateway
A legtöbb szolgáltatás nem elérhető közvetlenül az internetről. A hozzáférés az API Gateway-en keresztül történik, amely ellenőrzi a hozzáférési jogokat. Ez is a mi szolgáltatásunk, és ennek is vannak tesztjei.
Valós idejű értesítések
Az alkalmazás használja , hogy valós időben értesítéseket jelenítsen meg a felhasználónak. Ez az értesítési szolgáltatásban van megvalósítva. Ez közvetlenül elérhető az internetről, és magával feküdt a OAuth támogatással, mert nem volt célszerű a Web Socket támogatását beépíteni a Gateway-be az OAuth és az értesítési szolgáltatás integrálásához képest.
Ismert megközelítés a teszteléshez
Az egységtesztek átkonfigurálják a mock objektumokkal az olyan dolgokat, mint az adatbázis. Ha egy mikro-szolgáltatás például megpróbál egy rekordot létrehozni egy külső kulccsal rendelkező táblában, és a hivatkozott rekord nem létezik, akkor a kérés nem teljesíthető. Az egységtesztek ezt nem képesek észlelni.
A javasolják az in-memory adatbázis használatát és a mock objektumok bevezetését.
Az in-memory adatbázis az egyik olyan DBMS, amelyet az Entity Framework támogat. Kifejezetten tesztelés céljából készült. Az ilyen adatbázisban az adatok csak a felhasználó folyamatának befejezéséig tárolódnak. Itt nem szükséges táblázatokat létrehozni, és az adatok integritását nem ellenőrzik.
A mock-objektumok csak annyira modellezik a helyettesíthető osztályt, amennyire a tesztelő fejlesztő ezt megérti.
A Postgres automatikus indításának és a migráció végrehajtásának módja a teszt indításakor nem szerepel a Microsoft cikkében. Az én megoldásom ezt végrehajtja, és emellett nem ad hozzá semmilyen kódot a mikroszolgáltatáshoz, amely kifejezetten tesztelés céljára szolgál.
Térjünk át a megoldásra
A fejlesztés folyamán világossá vált, hogy a unit tesztek nem elegendőek a problémák időben való észlelésére, ezért úgy döntöttünk, hogy más megközelítést alkalmazunk.
A tesztkörnyezet beállítása
Az első feladat a tesztkörnyezet telepítése. A mikroszolgáltatás indításához szükséges lépések:
- A tesztelésre kiszolgáltatott szolgáltatást be kell állítani a helyi környezetre, a környezeti változókban meg kell adni az adatbázishoz és az AWS-hez való kapcsolódás részleteit;
- Indítsa el a Postgres-t és hajtsa végre a migrációt a Liquibase elindításával.
A relációs DBMS-ekben az adatok adatbázisba írása előtt létre kell hozni az adatmodell sémát, más szóval a táblázatokat. Az alkalmazás frissítésekor a táblázatokat az új verzió által használt formához kell igazítani, lehetőleg adatvesztés nélkül. Ezt migrációnak hívják. A táblázatok létrehozása egy eredetileg üres adatbázisban a migráció speciális esete. A migrációt be lehet építeni magába az alkalmazásba. A .NET és a NodeJS esetében is léteznek migrációs keretrendszerek. A mi esetünkben a biztonság érdekében a mikroszolgáltatások nem jogosultak az adatmodellek sémájának megváltoztatására, és a migrációt a Liquibase végzi. - Indítsa el az Amazon LocalStack-et. Ez az AWS szolgáltatások helyi futtatásának megvalósítása. A LocalStack-hez kész Docker Hub képmás áll rendelkezésre.
- Indítsa el a skriptet a szükséges entitások létrehozásához a LocalStack-ben. A Shell-skriptek az AWS CLI-t használják.
A projekt teszteléséhez a következőt használják . Ez korábban is létezett, de manuálisan indították, és a már a tesztkörnyezetre telepített alkalmazást tesztelték. Ez az eszköz lehetővé teszi tetszőleges HTTP(S) kérések küldését és a válaszok elvárásokkal való összehasonlítását. A kérések egy gyűjteménybe összegyűjtve futtathatók együtt.

Hogyan működik az automatikus teszt
A teszt során a Dockerben minden működik: a tesztelendő szolgáltatás, a Postgres, a migrációs eszköz, és a Postman, vagyis annak konzolos verziója – Newman.
A Docker számos problémát megold:
- Függetlenség a host konfigurációjától;
- Függőségek telepítése: a Docker letölti a képeket a Docker Hubról;
- A rendszer visszaállítása az alapállapotra: egyszerűen eltávolítjuk a konténereket.
Docker-compose összekapcsolja a konténereket egy virtuális hálózatban, amely elkülönített az internettől, ahol a konténerek domainnevek alapján találják meg egymást.
A tesztet shell-skript irányítja. Windows alatt git-bash-t használunk a teszt futtatásához. Így elegendő egyetlen skript Windowsra és Linuxra is. A Git és a Docker telepítve van minden fejlesztőnél a projektben. A Git Windowsra történő telepítésekor git-bash is telepítve van, tehát az is mindenkinek rendelkezésére áll.
A skript a következő lépéseket hajtja végre:
- Docker képek építése
docker-compose build - Az adatbázis és a LocalStack indítása
docker-compose up -d - Adatbázis migrálása és a LocalStack előkészítése
docker-compose run - A tesztelendő szolgáltatás indítása
docker-compose up -d - A teszt futtatása (Newman)
- Minden konténer leállítása
docker-compose down - Eredmények közzététele a Slack-en
Van egy chatünk, ahová a zöld pipa vagy a piros kereszt és a naplólink üzenetei érkeznek.
Ezekben a lépésekben a következő Docker képek szerepelnek:
- A tesztelendő szolgáltatás – ugyanaz a kép, mint a produkcióban. A teszt konfigurációja környezeti változókon keresztül történik.
- A Postgres, a Redis és a LocalStack számára kész képeket használunk a Docker Hubról. A Liquibase és a Newman számára is léteznek kész képek. Azokat építjük, hozzáadva a saját fájljainkat.
- A LocalStack előkészítéséhez egy kész AWS CLI képet használunk, amely alapján egy képet hozunk létre, amely tartalmaz egy skriptet.
Használva , nem szükséges Docker képet készíteni csupán fájlok hozzáadásához a konténerhez. Azonban a volumes nem megfelelő a mi környezetünkhöz, mert a Gitlab CI feladatok maguk is konténerekben futnak. Ebből a konténerből kezelhetjük a Dockert, de a volumes csak a host rendszer mappáit csatolják, nem egy másik konténerből.
Problémák, amelyekkel szembesülhetünk
Készültség várakoztatása
Amikor a szolgáltatás konténer indul, az még nem jelenti azt, hogy készen áll a kapcsolatok fogadására. Várni kell a kapcsolatra, hogy folytathassuk.
Ezt a feladatot néha skript segítségével oldják meg , ami várja a TCP kapcsolat létrehozásának lehetőségét. Azonban a LocalStack 502 Bad Gateway hibát adhat. Továbbá, sok szolgáltatásból áll, és ha az egyik készen áll, az nem mond semmit a többiről.
Megoldás: a LocalStack előkészítő szkriptjei, amelyek a 200-as választ várják az SQS-től és az SNS-től.
Párhuzamos feladatok ütközései
Több teszt is párhuzamosan futhat egy Docker-hoszton, ezért a konténerek és hálózatok neveinek egyedinek kell lenniük. Továbbá, a különböző ágakból származó tesztek is párhuzamosan futhatnak, ezért nem elég a saját neveiket megadni minden compose fájlban.
Megoldás: a szkript egyedi értéket állít be a COMPOSE_PROJECT_NAME változónak.
Windows sajátosságai
A Docker Windows alatti használatakor számos dologra szeretném felhívni a figyelmet, mivel ez a tapasztalat fontos a hibák megértéséhez.
- A konténerben lévő shell szkripteknek Linux végeken kell lennie.
A CR karakter a shellben szintaktikai hibát jelent. A hibaüzenetből nehéz kideríteni, hogy erről van szó. Ezeknek a szkripteknek a Windows alatti szerkesztésekor megfelelő szövegszerkesztő szükséges. Ezenkívül a verziókezelő rendszert helyesen kell beállítani.
Így állítható be a git:
git config core.autocrlf input- A Git-bash emulálja a Linux szabványos mappáit, és amikor exe fájlt (beleértve a docker.exe-t is) hívnak meg, az abszolút Linux utak helyett Windows utakat cserél. Ez azonban nincs értelme a nem helyi gépen található útvonalakra (vagy konténerbeli utakra). Ez a viselkedés nem kapcsolható ki.
Megoldás: kiegészíteni az útvonal elejét további per jel: \/\/bin helyett \/bin. A Linux érti az ilyen utakat, számára a több per jel is egyet jelent. De a git-bash nem ismeri fel ezeket az utakat és nem próbálja átalakítani őket.
Napló kimenet
A tesztek futtatása során örömmel látnám a naplókat a Newmantól és a tesztelt szolgáltatástól is. Mivel ezeknek a naplóeseményeknek a kapcsolataik van egymással, azok egy konzolon való egyesítése sokkal kényelmesebb, mint két külön fájl. A Newmant a következő paranccsal indítják: docker-compose run, és ezért a kimenete a konzolba kerül. Azonban meg kell oldani, hogy a szolgáltatás kimenete is oda kerüljön.
Az eredeti megoldás az volt, hogy docker-compose up flag nélkül -d, de a shell lehetőségeit felhasználva ezt a folyamatot a háttérbe küldtük:
docker-compose up <service> &Ez működött, amíg nem kellett a Docker naplókat egy külső szolgáltatásba küldeni. docker-compose up megszüntette a naplózást a konzolon. Azonban a parancs működött docker attach.
Megoldás:
docker attach --no-stdin ${COMPOSE_PROJECT_NAME}__1 &Azonosító ütközés a teszt iterációk során
A tesztek több iteráción keresztül futnak. A database nem kerül törlésre. Az adatbázisban lévő bejegyzések egyedi azonosítókkal rendelkeznek. Ha konkrét azonosítókat írunk a lekérdezésekbe, a második iterációnál ütközést kapunk.
Ennek elkerüléséhez, vagy az azonosítóknak egyedinek kell lenniük, vagy el kell törölni az összes, a teszt által létrehozott objektumot. Néhány objektum törlése nem megengedett, az előírások szerint.
Megoldás: GUID-ok generálása scriptel Postman-ban.
var uuid = require('uuid');
var myid = uuid.v4();
pm.environment.set('myUUID', myid);Ezután a lekérdezésben használja a karaktert {{myUUID}}, amely a változó értékére cserélődik.
Interakció a LocalStack-en keresztül
Ha a tesztelt szolgáltatás SQS sorozatot olvas vagy ír, ahhoz, hogy ezt ellenőrizzük, a tesztnek is kell, hogy dolgozzon ezzel a sorral.
Megoldás: lekérdezések Postman-ból a LocalStack-hez.
Az AWS szolgáltatásaik API-ja dokumentált, ami lehetővé teszi a lekérdezések végrehajtását SDK nélkül.
Ha a szolgáltatás ír a sorba, akkor olvassuk is, és ellenőrizzük az üzenet tartalmát.
Ha a szolgáltatás SNS-be küld üzeneteket, a LocalStack előkészítésekor egy sor is létrejön, és feliratkozik erre az SNS témára. Minden további lépés a fentieknek megfelelően zajlik.
Ha a szolgáltatásnak olvasnia kell egy üzenetet a sorból, akkor a teszt előző lépésében ezt az üzenetet írjuk a sorba.
HTTP lekérdezések tesztelése a tesztelt mikroszolgáltatásból
Egyes szolgáltatások HTTP-n dolgoznak valami mással, mint az AWS, és néhány AWS funkció nincs implementálva LocalStack-en.
Megoldás: ilyen esetekben segítséget nyújthat a , amelynek van egy kész képe a . A várt lekérdezéseket és válaszokat HTTP lekérdezéssel állítjuk be. Az API dokumentált, így a Postmanból végezzük a lekérdezéseket.
OAuth azonosítási és engedélyezési tesztelés
Használunk OAuth-ot és . A teszthez szükség van egy OAuth szolgáltatóra, amelyet helyben el tudunk indítani.
Az összes interakció a szolgáltatás és az OAuth szolgáltató között két lekérdezésre korlátozódik: először a konfigurációt kérjük le /.well-known/openid-configuration, majd a konfigurációból kérjük le a nyilvános kulcsot (JWKS). Mindez statikus tartalom.
Megoldás: a tesztünk OAuth szolgáltatója egy statikus tartalom szerver és két fájl rajta. Egy token generálva lett egyszer és el lett mentve a Git-be.
Jellemzők a SignalR tesztelésében
A Postman nem működik web socketekkel. A SignalR tesztelésére egy speciális eszköz készült.
A SignalR ügyfél nemcsak böngésző lehet. Létezik .NET Core-ra készült kliens könyvtár is. A .NET Core-on írt kliens kapcsolódik, hitelesíti magát és várakozik a megadott üzenetsorrendre. Ha váratlan üzenetet kap vagy a kapcsolat megszakad, a kliens 1-es kóddal leáll. Az utolsó várt üzenet megérkezésekor 0-ás kóddal zárul.
Egyidejűleg a Newman is fut. Több ügyfélt példányt indítanak el, hogy ellenőrizzék, hogy az üzenetek mindenkinek eljutnak.

Több kliens indításához az alábbi opciót használjuk —scale a docker-compose parancssorában.
A Postman indítása előtt a szkript megvárja, amíg minden kliens csatlakozik.
A csatlakozás megvárásának problémájával már találkoztunk. Ott szerverek voltak, itt pedig kliensek. Más megközelítésre van szükség.
Megoldás: a konténerben működő kliens a mechanizmust használja, hogy értesítse a hoszton lévő szkriptet az állapotáról. A kliens egy fájlt hoz létre egy meghatározott úton, mondjuk /healthcheck, amint a kapcsolat létrejön. A HealthCheck szkript a docker fájlban így néz ki:
HEALTHCHECK --interval=3s CMD if [ ! -e /healthcheck ]; then false; fiA parancs a docker inspect a konténernek a szokásos állapotát, az egészségi állapotát és a kilépési kódját mutatja.
A Newman befejezése után a szkript ellenőrzi, hogy az összes klienssel rendelkező konténer 0-ás kóddal befejeződött-e.
Boldogság létezik
Miután túljutottunk a fent említett nehézségeken, stabilan működő tesztek álltak rendelkezésünkre. A tesztek során minden szolgáltatás egységesen működik, interakcióban van az adatbázissal és az Amazon LocalStack-kel.
Ezek a tesztek védelmet nyújtanak a 30+ fejlesztőből álló csapat számára a komplex interakciókkal rendelkező alkalmazási hibák ellen, a 10+ mikroszolgáltatás gyakori telepítései során.
Forrás: habr.com
