W projektach związanych z rozwojem architektury mikroserwisowej CI/CD przechodzi z kategorii przyjemnej okazji do kategorii pilnej konieczności. Automatyczne testowanie jest integralną częścią ciągłej integracji, a kompetentne podejście może zapewnić zespołowi wiele przyjemnych wieczorów w gronie rodziny i przyjaciół. W przeciwnym razie istnieje ryzyko, że projekt nigdy nie zostanie ukończony.
Możliwe jest pokrycie całego kodu mikroserwisu testami jednostkowymi z próbnymi obiektami, ale to tylko częściowo rozwiązuje problem i pozostawia wiele pytań i trudności, szczególnie podczas testowania pracy z danymi. Jak zwykle najpilniejsze są testowanie spójności danych w relacyjnej bazie danych, testowanie pracy z usługami w chmurze oraz przyjęcie błędnych założeń podczas pisania mock obiektów.
Wszystko to i trochę więcej można rozwiązać testując cały mikroserwis w kontenerze Docker. Niewątpliwą zaletą zapewniającą ważność testów jest to, że testowane są te same obrazy Dockera, które trafiają do produkcji.
Automatyzacja tego podejścia stwarza szereg problemów, których rozwiązanie zostanie opisane poniżej:
- konflikty równoległych zadań w tym samym hoście dokującym;
- konflikty identyfikatorów w bazie danych podczas iteracji testów;
- oczekiwanie, aż mikroserwisy będą gotowe;
- łączenie i wysyłanie logów do systemów zewnętrznych;
- testowanie wychodzących żądań HTTP;
- testowanie gniazd internetowych (przy użyciu SignalR);
- testowanie uwierzytelniania i autoryzacji OAuth.
Ten artykuł jest oparty na
W tym artykule opowiem jak za pomocą skryptu uruchomić testowaną usługę, bazę danych i usługi Amazon AWS w Dockerze, następnie przeprowadzić testy na Postmanie i po ich zakończeniu zatrzymać i usunąć utworzone kontenery. Testy są wykonywane przy każdej zmianie kodu. W ten sposób mamy pewność, że każda wersja poprawnie współpracuje z bazą danych i usługami AWS.
Ten sam skrypt jest uruchamiany zarówno przez samych programistów na komputerach stacjonarnych z systemem Windows, jak i przez serwer Gitlab CI pod Linuksem.
Aby było to uzasadnione, wprowadzenie nowych testów nie powinno wymagać instalacji dodatkowych narzędzi ani na komputerze programisty, ani na serwerze, na którym uruchamiane są testy na zatwierdzeniu. Docker rozwiązuje ten problem.
Test musi zostać uruchomiony na serwerze lokalnym z następujących powodów:
- Sieć nigdy nie jest całkowicie niezawodna. Spośród tysiąca próśb jedno może się nie powieść;
W takim przypadku test automatyczny nie zadziała, praca zostanie zatrzymana i będziesz musiał szukać przyczyny w logach; - Niektóre usługi stron trzecich nie zezwalają na zbyt częste żądania.
Ponadto korzystanie ze stojaka jest niepożądane, ponieważ:
- Stanowisko może zostać zepsute nie tylko przez działający na nim zły kod, ale także przez dane, których prawidłowy kod nie jest w stanie przetworzyć;
- Bez względu na to, jak bardzo staramy się przywrócić wszystkie zmiany wprowadzone przez test podczas samego testu, coś może pójść nie tak (w przeciwnym razie po co testować?).
O organizacji projektu i procesu
Nasza firma stworzyła mikroserwisową aplikację internetową działającą w Dockerze w chmurze Amazon AWS. W projekcie zastosowano już testy jednostkowe, ale często pojawiały się błędy, których testy jednostkowe nie wykryły. Konieczne było przetestowanie całego mikroserwisu wraz z bazą danych i usługami Amazon.
W projekcie zastosowano standardowy proces ciągłej integracji, który obejmuje testowanie mikroserwisu przy każdym zatwierdzeniu. Po przydzieleniu zadania programista wprowadza zmiany w mikroserwisie, testuje go ręcznie i uruchamia wszystkie dostępne testy automatyczne. W razie potrzeby programista zmienia testy. Jeśli nie zostaną znalezione żadne problemy, zostanie dokonane zatwierdzenie gałęzi tego problemu. Po każdym zatwierdzeniu na serwerze automatycznie uruchamiane są testy. Połączenie we wspólną gałąź i uruchomienie na niej automatycznych testów następuje po pomyślnym przeglądzie. Jeśli testy na gałęzi współdzielonej zakończą się pomyślnie, usługa zostanie automatycznie zaktualizowana w środowisku testowym w usłudze Amazon Elastic Container Service (bench). Podstawka jest niezbędna wszystkim programistom i testerom i nie zaleca się jej łamania. Testerzy w tym środowisku sprawdzają poprawkę lub nową funkcję, wykonując testy ręczne.
Architektura projektu
Aplikacja składa się z kilkunastu usług. Część z nich jest napisana w .NET Core, a część w NodeJ. Każda usługa działa w kontenerze Docker w usłudze Amazon Elastic Container Service. Każdy ma własną bazę danych Postgres, a niektóre mają także Redis. Nie ma wspólnych baz danych. Jeśli kilka usług potrzebuje tych samych danych, wówczas dane te w przypadku ich zmiany są przesyłane do każdej z tych usług za pośrednictwem SNS (Simple Notification Service) i SQS (Amazon Simple Queue Service), a usługi zapisują je we własnych, odrębnych bazach danych.
SQS i SNS
SQS umożliwia umieszczanie wiadomości w kolejce i odczytywanie wiadomości z kolejki za pomocą protokołu HTTPS.
Jeśli z jednej kolejki odczytuje kilka usług, wówczas każda wiadomość dociera tylko do jednej z nich. Jest to przydatne w przypadku uruchamiania kilku wystąpień tej samej usługi w celu rozłożenia obciążenia między nimi.
Jeśli chcesz, aby każda wiadomość była dostarczana do wielu usług, każdy odbiorca musi mieć własną kolejkę, a SNS jest potrzebny do duplikowania wiadomości w wielu kolejkach.
W SNS tworzysz temat i subskrybujesz go np. kolejkę SQS. Możesz wysyłać wiadomości do tematu. W takim przypadku wiadomość jest wysyłana do każdej kolejki subskrybowanej w tym temacie. SNS nie ma metody czytania wiadomości. Jeśli podczas debugowania lub testowania chcesz dowiedzieć się, co jest wysyłane do SNS, możesz utworzyć kolejkę SQS, zasubskrybować ją do żądanego tematu i przeczytać kolejkę.
API Gateway
Większość usług nie jest bezpośrednio dostępna z Internetu. Dostęp odbywa się poprzez API Gateway, który sprawdza uprawnienia dostępu. To także nasza usługa i dla niej też są testy.
Powiadomienia w czasie rzeczywistym
Aplikacja korzysta
Dobrze znane podejście do testowania
Testy jednostkowe zastępują takie rzeczy jak baza danych obiektami próbnymi. Jeśli na przykład mikrousługa próbuje utworzyć rekord w tabeli z kluczem obcym, a rekord, do którego odwołuje się ten klucz, nie istnieje, wówczas żądanie nie może zostać zrealizowane. Testy jednostkowe nie mogą tego wykryć.
В
Baza danych w pamięci jest jednym z systemów DBMS obsługiwanych przez Entity Framework. Został stworzony specjalnie do testów. Dane w takiej bazie danych przechowywane są jedynie do czasu zakończenia procesu korzystającego z nich. Nie wymaga tworzenia tabel i nie sprawdza integralności danych.
Obiekty próbne modelują klasę, którą zastępują, tylko w takim stopniu, w jakim twórca testu rozumie, jak to działa.
W artykule firmy Microsoft nie określono, jak zmusić Postgres do automatycznego uruchamiania i przeprowadzania migracji po uruchomieniu testu. Moje rozwiązanie to robi, a ponadto nie dodaje do samego mikroserwisu żadnego kodu specjalnie do testów.
Przejdźmy do rozwiązania
Podczas procesu tworzenia oprogramowania stało się jasne, że testy jednostkowe nie wystarczą do znalezienia wszystkich problemów w odpowiednim czasie, dlatego zdecydowano się podejść do tego problemu z innej perspektywy.
Konfigurowanie środowiska testowego
Pierwszym zadaniem jest wdrożenie środowiska testowego. Kroki wymagane do uruchomienia mikroserwisu:
- Skonfiguruj testowaną usługę dla środowiska lokalnego, w zmiennych środowiskowych podaj szczegóły połączenia z bazą danych i AWS;
- Uruchom Postgres i wykonaj migrację, uruchamiając Liquibase.
W relacyjnych systemach DBMS przed zapisaniem danych do bazy danych należy utworzyć schemat danych, czyli inaczej tabele. Podczas aktualizacji aplikacji tabele należy doprowadzić do postaci używanej w nowej wersji i najlepiej bez utraty danych. Nazywa się to migracją. Tworzenie tabel w początkowo pustej bazie danych jest szczególnym przypadkiem migracji. Migrację można wbudować w samą aplikację. Zarówno .NET, jak i NodeJS mają platformy migracji. W naszym przypadku ze względów bezpieczeństwa mikroserwisy pozbawione są prawa do zmiany schematu danych, a migracja odbywa się za pomocą Liquibase. - Uruchom Amazon LocalStack. Jest to implementacja usług AWS do uruchamiania w domu. Istnieje gotowy obraz dla LocalStack na Docker Hub.
- Uruchom skrypt, aby utworzyć niezbędne encje w LocalStack. Skrypty powłoki korzystają z interfejsu CLI AWS.
Używany do testów w projekcie
Jak działa test automatyczny?
Podczas testu w Dockerze wszystko działa: testowana usługa, Postgres, narzędzie do migracji oraz Postman, a właściwie jego konsolowa wersja – Newman.
Docker rozwiązuje szereg problemów:
- Niezależność od konfiguracji hosta;
- Instalowanie zależności: Docker pobiera obrazy z Docker Hub;
- Przywrócenie systemu do pierwotnego stanu: po prostu usunięcie pojemników.
komponowanie dokera łączy kontenery w odizolowaną od Internetu sieć wirtualną, w której kontenery odnajdują się nawzajem według nazw domen.
Test jest kontrolowany przez skrypt powłoki. Aby uruchomić test w systemie Windows, używamy git-bash. Zatem jeden skrypt wystarczy zarówno dla systemu Windows, jak i Linux. Git i Docker są instalowane przez wszystkich programistów projektu. Podczas instalacji Gita w systemie Windows instalowany jest git-bash, więc każdy też to ma.
Skrypt wykonuje następujące kroki:
- Tworzenie obrazów dokowanych
docker-compose build
- Uruchomienie bazy danych i LocalStack
docker-compose up -d <контейнер>
- Migracja bazy danych i przygotowanie LocalStack
docker-compose run <контейнер>
- Uruchomienie testowanej usługi
docker-compose up -d <сервис>
- Uruchamianie testu (Newman)
- Zatrzymanie wszystkich kontenerów
docker-compose down
- Publikowanie wyników w Slacku
Mamy czat, na który trafiają wiadomości z zielonym znacznikiem lub czerwonym krzyżykiem i linkiem do dziennika.
W tych krokach biorą udział następujące obrazy platformy Docker:
- Testowana usługa ma taki sam obraz jak w przypadku produkcji. Konfiguracja testu odbywa się za pomocą zmiennych środowiskowych.
- W przypadku Postgres, Redis i LocalStack wykorzystywane są gotowe obrazy z Docker Hub. Istnieją również gotowe obrazy dla Liquibase i Newmana. Budujemy nasz na ich szkielecie, dodając tam nasze pliki.
- Aby przygotować LocalStack, korzystasz z gotowego obrazu AWS CLI i tworzysz obraz zawierający oparty na nim skrypt.
Korzystanie z
Problemy, które możesz napotkać
Czekam na gotowość
To, że kontener z usługą jest uruchomiony, nie oznacza, że jest gotowy do przyjmowania połączeń. Musisz poczekać, aż połączenie będzie kontynuowane.
Czasami problem ten rozwiązuje się za pomocą skryptu
decyzja: Skrypty udostępniające LocalStack, które czekają na 200 odpowiedzi zarówno z SQS, jak i SNS.
Równoległe konflikty zadań
Na tym samym hoście Dockera można uruchomić wiele testów jednocześnie, dlatego nazwy kontenerów i sieci muszą być unikalne. Co więcej, testy z różnych gałęzi tej samej usługi mogą być uruchamiane jednocześnie, dlatego nie wystarczy wpisanie ich nazw w każdym pliku tworzenia.
decyzja: Skrypt ustawia zmienną COMPOSE_PROJECT_NAME na unikalną wartość.
Cechy okien
Jest wiele rzeczy, na które chcę zwrócić uwagę podczas korzystania z Dockera w systemie Windows, ponieważ te doświadczenia są ważne dla zrozumienia przyczyn występowania błędów.
- Skrypty powłoki w kontenerze muszą mieć końcówki linii systemu Linux.
Symbol powłoki CR jest błędem składniowym. Na podstawie komunikatu o błędzie trudno stwierdzić, czy tak jest. Do edycji takich skryptów w systemie Windows potrzebny jest odpowiedni edytor tekstu. Ponadto system kontroli wersji musi być odpowiednio skonfigurowany.
Tak jest skonfigurowany git:
git config core.autocrlf input
- Git-bash emuluje standardowe foldery Linuksa i podczas wywoływania pliku exe (w tym docker.exe) zastępuje bezwzględne ścieżki Linuksa ścieżkami Windows. Nie ma to jednak sensu w przypadku ścieżek, które nie znajdują się na komputerze lokalnym (lub ścieżek w kontenerze). Tego zachowania nie można wyłączyć.
decyzja: dodaj dodatkowy ukośnik na początku ścieżki: //bin zamiast /bin. Linux rozumie takie ścieżki; dla niego kilka ukośników jest tym samym. Ale git-bash nie rozpoznaje takich ścieżek i nie próbuje ich konwertować.
Dane wyjściowe dziennika
Podczas uruchamiania testów chciałbym zobaczyć logi zarówno Newmana, jak i testowanej usługi. Ponieważ zdarzenia tych logów są ze sobą powiązane, połączenie ich w jednej konsoli jest znacznie wygodniejsze niż dwa osobne pliki. Newman uruchamia poprzez uruchomienie tworzenia dokera, a zatem jego dane wyjściowe trafiają do konsoli. Pozostaje tylko upewnić się, że dane wyjściowe usługi również tam trafią.
Oryginalne rozwiązanie było zrobić dokuj-skomponuj bez flagi -d, ale korzystając z możliwości powłoki, wyślij ten proces do tła:
docker-compose up <service> &
Działało to do czasu, gdy konieczne było wysyłanie dzienników z Dockera do usługi strony trzeciej. dokuj-skomponuj przestał wysyłać logi do konsoli. Zespół jednak działał dokowanie dokuje.
decyzja:
docker attach --no-stdin ${COMPOSE_PROJECT_NAME}_<сервис>_1 &
Konflikt identyfikatorów podczas iteracji testu
Testy przeprowadzane są w kilku iteracjach. Baza danych nie została wyczyszczona. Rekordy w bazie posiadają unikalne identyfikatory. Jeśli w żądaniach zapiszemy konkretne identyfikatory, już w drugiej iteracji otrzymamy konflikt.
Aby tego uniknąć, albo identyfikatory muszą być unikalne, albo wszystkie obiekty utworzone w wyniku testu muszą zostać usunięte. Niektórych obiektów nie można usunąć ze względu na wymagania.
decyzja: generuj identyfikatory GUID za pomocą skryptów Postmana.
var uuid = require('uuid');
var myid = uuid.v4();
pm.environment.set('myUUID', myid);
Następnie użyj symbolu w zapytaniu {{myUUID}}, która zostanie zastąpiona wartością zmiennej.
Współpraca poprzez LocalStack
Jeśli testowana usługa czyta lub zapisuje do kolejki SQS, to aby to zweryfikować, sam test musi również działać z tą kolejką.
decyzja: żądania od Postmana do LocalStack.
Interfejs API usług AWS jest udokumentowany, co pozwala na wykonywanie zapytań bez pakietu SDK.
Jeśli usługa zapisuje do kolejki, to czytamy ją i sprawdzamy zawartość wiadomości.
Jeśli usługa wysyła wiadomości do SNS, na etapie przygotowania LocalStack również tworzy kolejkę i subskrybuje ten temat SNS. Potem wszystko sprowadza się do tego, co opisano powyżej.
Jeśli usługa potrzebuje odczytać wiadomość z kolejki, to w poprzednim kroku testowym zapisujemy tę wiadomość do kolejki.
Testowanie żądań HTTP pochodzących z testowanej mikrousługi
Niektóre usługi działają przez HTTP z czymś innym niż AWS, a niektóre funkcje AWS nie są zaimplementowane w LocalStack.
decyzja: w takich przypadkach może pomóc
Testowanie uwierzytelniania i autoryzacji OAuth
Używamy OAuth i
Cała interakcja pomiędzy usługą a dostawcą OAuth sprowadza się do dwóch żądań: po pierwsze, żądana jest konfiguracja /.well-known/openid-configuration, a następnie żądany jest klucz publiczny (JWKS) pod adresem z konfiguracji. Wszystko to jest treścią statyczną.
decyzja: Nasz testowy dostawca OAuth to serwer zawartości statycznej i znajdujące się na nim dwa pliki. Token jest generowany raz i przekazywany do Git.
Funkcje testowania sygnalizującego
Postman nie współpracuje z websocketami. Do testowania SignalR-a stworzono specjalne narzędzie.
Klient sygnalizujący może być czymś więcej niż tylko przeglądarką. W platformie .NET Core dostępna jest biblioteka kliencka. Klient napisany w .NET Core nawiązuje połączenie, zostaje uwierzytelniony i czeka na określoną sekwencję komunikatów. Jeśli otrzyma nieoczekiwaną wiadomość lub połączenie zostanie utracone, klient wyjdzie z kodem 1. Jeśli otrzyma ostatnią oczekiwaną wiadomość, klient wyjdzie z kodem 0.
Newman pracuje jednocześnie z klientem. Uruchomiono kilku klientów, którzy sprawdzają, czy wiadomości docierają do wszystkich, którzy ich potrzebują.
Aby uruchomić wielu klientów, użyj opcji --skala w wierszu poleceń docker-compose.
Przed uruchomieniem skrypt Postman czeka, aż wszyscy klienci nawiążą połączenie.
Spotkaliśmy się już z problemem oczekiwania na połączenie. Ale były serwery, a tu jest klient. Potrzebne jest inne podejście.
decyzja: klient w kontenerze korzysta z mechanizmu
HEALTHCHECK --interval=3s CMD if [ ! -e /healthcheck ]; then false; fi
Zespół Inspekcja dokera Pokazuje stan normalny, stan kondycji i kod wyjścia kontenera.
Po zakończeniu działania Newmana skrypt sprawdza, czy wszystkie kontenery z klientem zostały zakończone, podając kod 0.
Szczęście istnieje
Po pokonaniu opisanych powyżej trudności mieliśmy zestaw testów stabilnego działania. W testach każda usługa działa jako pojedyncza jednostka, współdziałając z bazą danych i Amazon LocalStack.
Testy te chronią zespół ponad 30 programistów przed błędami w aplikacji przy złożonej interakcji ponad 10 mikrousług z częstymi wdrożeniami.
Źródło: www.habr.com