Dans les projets liĂ©s au dĂ©veloppement d'architectures de microservices, le CI/CD passe d'une option agrĂ©able Ă une nĂ©cessitĂ© urgente. Les tests automatisĂ©s sont une partie intĂ©grante de l'intĂ©gration continue, et une approche adĂ©quate peut offrir Ă l'Ă©quipe de nombreuses soirĂ©es agrĂ©ables avec la famille et les amis. Dans le cas contraire, le projet risque de ne jamais ĂȘtre achevĂ©.
On peut couvrir tout le code d'un microservice avec des tests unitaires et des objets fictifs, mais cela ne résout qu'une partie du problÚme et laisse de nombreuses questions et complexités, notamment lors des tests liés aux données. Comme toujours, les plus épineux sont : le test de la cohérence des données dans la base de données relationnelle, le test de l'interaction avec les services cloud et les hypothÚses erronées lors de l'écriture des objets fictifs.
Tout cela, et un peu plus, se rĂ©sout par le test de l'intĂ©gralitĂ© d'un microservice dans un conteneur Docker. Un avantage incontestable pour assurer la validitĂ© des tests est que les mĂȘmes images Docker qui seront mises en production sont soumises aux tests.
L'automatisation de cette approche pose un certain nombre de problÚmes, dont la solution sera décrite un peu plus bas :
- conflits de tĂąches parallĂšles sur un mĂȘme hĂŽte Docker ;
- conflits d'identifiants dans la base de données lors des itérations de test ;
- attente de la disponibilité des microservices ;
- agrégation et sortie des journaux vers des systÚmes externes ;
- test des requĂȘtes HTTP sortantes ;
- test des websockets (avec SignalR) ;
- test de l'authentification et de l'autorisation OAuth.
Ceci est un article inspiré de SECR 2019. Donc, pour ceux qui n'ont pas envie de lire, .

Dans cet article, je vais expliquer comment, Ă l'aide d'un script, lancer dans Docker le service Ă tester, la base de donnĂ©es et les services Amazon AWS, puis exĂ©cuter les tests sur Postman et, une fois terminĂ©s, arrĂȘter et supprimer les conteneurs créés. Les tests sont exĂ©cutĂ©s Ă chaque modification du code. Ainsi, nous nous assurons que chaque version fonctionne correctement avec la base de donnĂ©es et les services AWS.
Le mĂȘme script est lancĂ© Ă la fois par les dĂ©veloppeurs sur leurs postes de travail Windows et par le serveur Gitlab CI sous Linux.
Pour que l'intĂ©gration de nouveaux tests soit justifiĂ©e, elle ne doit nĂ©cessiter l'installation d'outils supplĂ©mentaires ni sur l'ordinateur du dĂ©veloppeur, ni sur le serveur oĂč les tests sont lancĂ©s lors des commits. Docker rĂ©sout ce problĂšme.
Le test doit fonctionner sur un serveur local pour les raisons suivantes :
- Le rĂ©seau n'est jamais totalement fiable. Sur mille requĂȘtes, une peut Ă©chouer ;
Dans ce cas, le test automatique ne passera pas, le fonctionnement s'arrĂȘtera, et il faudra rechercher la cause dans les logs ; - Des requĂȘtes trop frĂ©quentes ne sont pas acceptĂ©es par certains services tiers.
De plus, il est indésirable d'utiliser l'environnement de test pour les raisons suivantes :
- Un mauvais code fonctionnant sur l'environnement peut non seulement le briser, mais aussi des données que le code correct ne peut pas traiter ;
- Peu importe combien nous essayons de revenir sur toutes les modifications effectuées par le test, quelque chose peut mal se passer pendant le test (sinon, pourquoi tester ?).
Ă propos du projet et de l'organisation du processus
Notre entreprise a développé une application web en microservices, fonctionnant dans Docker sur le cloud Amazon AWS. Le projet utilisait déjà des tests unitaires, mais des erreurs survenaient souvent que les tests unitaires ne détectaient pas. Il était nécessaire de tester un microservice entier avec sa base de données et les services Amazon.
Le projet suit un processus standard d'intégration continue, incluant des tests du microservice à chaque commit. AprÚs l'assignation d'une tùche, le développeur apporte des modifications au microservice, le teste manuellement et exécute tous les tests automatiques disponibles. Si nécessaire, le développeur modifie les tests. Si aucun problÚme n'est détecté, un commit est effectué dans la branche de cette tùche. AprÚs chaque commit, des tests sont automatiquement lancés sur le serveur. La fusion dans la branche principale et l'exécution des tests automatiques sur celle-ci se font aprÚs une révision réussie. Si les tests sur la branche principale passent, le service est automatiquement mis à jour dans l'environnement de test sur Amazon Elastic Container Service (l'environnement de test). Cet environnement est nécessaire pour tous les développeurs et testeurs, et il est indésirable de le briser. Les testeurs vérifient dans cet environnement un correctif ou une nouvelle fonctionnalité en effectuant des tests manuels.
Architecture du projet

L'application est composĂ©e de plus de dix services. Certains d'entre eux sont Ă©crits en .NET Core, d'autres en NodeJs. Chaque service fonctionne dans un conteneur Docker sur Amazon Elastic Container Service. Chacun a sa propre base de donnĂ©es Postgres, et certains utilisent Ă©galement Redis. Il n'y a pas de bases de donnĂ©es communes. Si plusieurs services ont besoin des mĂȘmes donnĂ©es, ces donnĂ©es sont transmises Ă chacun de ces services via SNS (Simple Notification Service) et SQS (Amazon Simple Queue Service) au moment de leur modification, et les services les enregistrent dans leurs bases de donnĂ©es distinctes.
SQS et SNS
SQS permet, via le protocole HTTPS, d'ajouter des messages dans une file d'attente et de lire les messages de cette file.
Si plusieurs services lisent une mĂȘme file d'attente, chaque message est destinĂ© Ă un seul d'entre eux. Cela est utile lors du lancement de plusieurs instances d'un mĂȘme service pour rĂ©partir la charge entre elles.
Si vous souhaitez que chaque message soit livré à plusieurs services, chaque destinataire doit avoir sa propre file d'attente, et pour dupliquer des messages dans plusieurs files d'attente, SNS est nécessaire.
Dans SNS, vous créez un topic et vous abonnez, par exemple, une file SQS. Vous pouvez envoyer des messages au topic. Dans ce cas, le message est envoyé à chaque file abonnée à ce topic. SNS n'a pas de méthode pour lire les messages. Si, lors du débogage ou des tests, vous devez savoir ce qui est envoyé à SNS, vous pouvez créer une file SQS, l'abonner au topic souhaité et lire la file.

API Gateway
La plupart des services ne sont pas directement accessibles depuis Internet. L'accÚs se fait via API Gateway, qui vérifie les droits d'accÚs. C'est également notre service, et il y a aussi des tests pour cela.
Notifications en temps réel
L'application utilise , pour montrer aux utilisateurs les notifications en temps réel. Cela est réalisé dans le service de notifications. Il est accessible directement depuis Internet et fonctionne avec OAuth, car intégrer le support des WebSockets dans le Gateway s'est révélé peu pratique par rapport à l'intégration d'OAuth et du service de notifications.
Approche bien connue du test
Les tests unitaires remplacent des Ă©lĂ©ments tels que la base de donnĂ©es par des objets mock. Si un microservice, par exemple, essaie de crĂ©er une entrĂ©e dans une table avec une clĂ© Ă©trangĂšre, mais que l'entrĂ©e Ă laquelle cette clĂ© se rĂ©fĂšre n'existe pas, alors la requĂȘte ne peut pas ĂȘtre exĂ©cutĂ©e. Les tests unitaires ne peuvent pas le dĂ©tecter.
Dans propose d'utiliser une base de données en mémoire et d'injecter des objets mock.
La base de données en mémoire est l'un des SGBD pris en charge par Entity Framework. Elle est conçue spécialement pour les tests. Les données dans une telle base ne sont conservées que jusqu'à la fin du processus qui l'utilise. Il n'est pas nécessaire de créer des tables, et l'intégrité des données n'est pas vérifiée.
Les objets factices simulent une classe substituable juste dans la mesure oĂč le dĂ©veloppeur du test comprend son fonctionnement.
La façon d'assurer le démarrage automatique de Postgres et d'effectuer la migration lors du démarrage du test n'est pas précisée dans l'article de Microsoft. Ma solution le fait et, de plus, aucun code n'est ajouté dans le microservice spécialement pour les tests.
Passons Ă la solution
Au cours du développement, il est devenu clair que les tests unitaires ne suffisent pas pour identifier rapidement tous les problÚmes, il a donc été décidé d'aborder cette question différemment.
Configuration de l'environnement de test
La premiÚre tùche consiste à déployer l'environnement de test. Voici les étapes nécessaires pour lancer le microservice :
- Configurer le service à tester pour l'environnement local, les variables d'environnement spécifient les informations d'identification pour se connecter à la base et à AWS ;
- Lancer Postgres et effectuer la migration en exécutant Liquibase.
Dans les SGBD relationnels, avant d'enregistrer des donnĂ©es dans la base, il est nĂ©cessaire de crĂ©er un schĂ©ma de donnĂ©es, en d'autres termes, des tables. Lors de la mise Ă jour de l'application, les tables doivent ĂȘtre adaptĂ©es Ă la forme utilisĂ©e par la nouvelle version, idĂ©alement sans perte de donnĂ©es. Cela s'appelle une migration. La crĂ©ation de tables dans une base initialement vide est un cas particulier de migration. La migration peut ĂȘtre intĂ©grĂ©e dans l'application elle-mĂȘme. Tanto dans .NET que dans NodeJS, il existe des frameworks pour la migration. Dans notre cas, pour des raisons de sĂ©curitĂ©, les microservices n'ont pas le droit de modifier le schĂ©ma de donnĂ©es, et la migration est effectuĂ©e Ă l'aide de Liquibase. - Lancer Amazon LocalStack. C'est une implĂ©mentation des services AWS Ă exĂ©cuter localement. Pour LocalStack, il existe une image prĂȘte Ă l'emploi sur Docker Hub.
- Exécuter le script pour créer dans LocalStack les entités nécessaires. Les scripts Shell utilisent AWS CLI.
Pour les tests, le projet utilise . Il Ă©tait dĂ©jĂ utilisĂ© auparavant, mais son lancement se faisait manuellement et l'application Ă©tait testĂ©e une fois dĂ©ployĂ©e sur l'environnement. Cet outil permet d'effectuer des requĂȘtes HTTP(S) arbitraires et de vĂ©rifier la conformitĂ© des rĂ©ponses avec les attentes. Les requĂȘtes sont regroupĂ©es dans une collection et l'ensemble de la collection peut ĂȘtre lancĂ© en une seule fois.

Comment fonctionne le test automatique
Pendant le test, tout fonctionne dans Docker : le service testé, Postgres, l'outil de migration et Postman, ou plutÎt sa version en ligne de commande - Newman.
Docker résout un certain nombre de problÚmes :
- Indépendance par rapport à la configuration de l'hÎte ;
- Installation des dépendances : Docker télécharge les images depuis Docker Hub ;
- Retour du systÚme à l'état initial : il suffit de supprimer les conteneurs.
Docker-compose il regroupe les conteneurs dans un rĂ©seau virtuel, isolĂ© d'Internet, oĂč les conteneurs se trouvent les uns les autres par leurs noms de domaine.
Le test est géré par un script shell. Pour exécuter le test sous Windows, nous utilisons git-bash. Ainsi, un seul script suffit à la fois pour Windows et Linux. Git et Docker sont installés chez tous les développeurs du projet. Lors de l'installation de Git sous Windows, git-bash est installé, donc tout le monde l'a aussi.
Le script exécute les étapes suivantes :
- Construction des images Docker
pour construire les images et - Démarrage de la base de données et de LocalStack
docker-compose up -d - Migration de la base de données et préparation de LocalStack
docker-compose run - Démarrage du service testé
docker-compose up -d - Exécution du test (Newman)
- ArrĂȘt de tous les conteneurs
docker-compose down - Publication des résultats sur Slack
Nous avons un chat oĂč arrivent les messages avec une coche verte ou une croix rouge et un lien vers le log.
Les étapes suivantes impliquent les images Docker suivantes :
- Le service testĂ© est la mĂȘme image que pour la production. La configuration pour le test se fait via des variables d'environnement.
- Pour Postgres, Redis et LocalStack, nous utilisons des images prĂȘtes Ă partir de Docker Hub. Il existe Ă©galement des images prĂȘtes pour Liquibase et Newman. Nous construisons les nĂŽtres sur leur base, en y ajoutant nos fichiers.
- Pour prĂ©parer LocalStack, nous utilisons une image prĂȘte de l'AWS CLI, sur la base de laquelle une image contenant le script est créée.
En utilisant , il n'est pas nĂ©cessaire de construire une image Docker juste pour ajouter des fichiers dans le conteneur. Cependant, les volumes ne conviennent pas Ă notre environnement, car les tĂąches Gitlab CI s'exĂ©cutent elles-mĂȘmes dans des conteneurs. Ă partir de ce type de conteneur, nous pouvons gĂ©rer Docker, mais les volumes montent uniquement des dossiers de l'hĂŽte, et non d'un autre conteneur.
ProblĂšmes que l'on peut rencontrer
Attente de disponibilité
Lorsque le conteneur avec le service est dĂ©marrĂ©, cela ne signifie pas qu'il est prĂȘt Ă accepter des connexions. Il faut attendre la connexion pour continuer.
Cette tĂąche est parfois rĂ©solue Ă l'aide d'un script , qui attend la possibilitĂ© d'Ă©tablir une connexion TCP. Cependant, LocalStack peut renvoyer une erreur 502 Bad Gateway. De plus, il se compose de nombreux services, et si l'un d'eux est prĂȘt, cela ne dit rien sur les autres.
Solution: scripts de préparation de LocalStack, qui attendent une réponse 200 à la fois de SQS et de SNS.
Conflits de tĂąches parallĂšles
Plusieurs tests peuvent s'exĂ©cuter simultanĂ©ment sur un mĂȘme hĂŽte Docker, donc les noms des conteneurs et des rĂ©seaux doivent ĂȘtre uniques. De plus, les tests issus de diffĂ©rentes branches d'un mĂȘme service peuvent Ă©galement fonctionner en mĂȘme temps, donc il n'est pas suffisant de dĂ©finir des noms dans chaque fichier compose.
Solution: le script attribue une valeur unique Ă la variable COMPOSE_PROJECT_NAME.
Particularités de Windows
Lors de l'utilisation de Docker sur Windows, il y a plusieurs points que je voudrais souligner, car cette expérience est importante pour comprendre les raisons des erreurs.
- Les scripts shell dans le conteneur doivent avoir des fins de ligne en format Linux.
Le symbole CR pour le shell est une erreur de syntaxe. D'aprĂšs le message d'erreur, il est difficile de comprendre que c'est Ă cause de cela. Lors de l'Ă©dition de tels scripts sous Windows, un Ă©diteur de texte appropriĂ© est nĂ©cessaire. De plus, le systĂšme de contrĂŽle de version doit ĂȘtre configurĂ© correctement.
Voici comment configurer git :
git config core.autocrlf input- Git-bash Ă©mule les rĂ©pertoires standards de Linux et, lors de l'appel d'un fichier exe (y compris docker.exe), il remplace les chemins absolus Linux par des chemins Windows. Cependant, cela n'a pas de sens pour les chemins non situĂ©s sur la machine locale (ou dans le conteneur). Ce comportement ne peut pas ĂȘtre dĂ©sactivĂ©.
Solution: ajouter un slash supplémentaire au début du chemin : \/\/bin au lieu de \/bin. Linux comprend de tels chemins, pour lui plusieurs slashes sont équivalents à un seul. Mais git-bash ne reconnaßt pas ces chemins et n'essaie pas de les convertir.
Sortie des logs
Lors de l'exĂ©cution des tests, il serait souhaitable de voir les logs Ă la fois de Newman et du service testĂ©. Ătant donnĂ© que les Ă©vĂ©nements de ces logs sont liĂ©s, les combiner dans une seule console est beaucoup plus pratique que deux fichiers sĂ©parĂ©s. Newman se lance via docker-compose run, et donc sa sortie apparaĂźt dans la console. Il reste Ă faire en sorte que la sortie du service y parvienne Ă©galement.
La solution initiale consistait à faire docker-compose up sans le flag -d, mais, en utilisant les possibilités du shell, d'envoyer ce processus en arriÚre-plan :
docker-compose up <service> &Cela fonctionnait tant qu'il n'a pas été nécessaire d'envoyer les logs de Docker vers un service tiers. docker-compose up a cessé d'afficher les journaux dans la console. Cependant, la commande fonctionnait docker attach.
Solution:
docker attach --no-stdin ${COMPOSE_PROJECT_NAME}_<service>_1 &Conflit d'identifiants lors des itérations du test
Les tests s'exĂ©cutent Ă plusieurs itĂ©rations. La base n'est pas effacĂ©e. Les enregistrements dans la base ont des ID uniques. Si nous Ă©crivons des ID spĂ©cifiques dans les requĂȘtes, nous obtiendrons un conflit lors de la deuxiĂšme itĂ©ration.
Pour Ă©viter cela, soit les ID doivent ĂȘtre uniques, soit nous devons supprimer tous les objets créés par le test. Certains objets ne peuvent pas ĂȘtre supprimĂ©s, conformĂ©ment aux exigences.
Solution: générer des GUID par des scripts dans Postman.
var uuid = require('uuid');
var myid = uuid.v4();
pm.environment.set('myUUID', myid);Ensuite, dans la requĂȘte, utiliser le symbole {{myUUID}}, qui sera remplacĂ© par la valeur de la variable.
Interaction via LocalStack
Si le service testé lit une file d'attente SQS ou y écrit, le test doit également fonctionner avec cette file.
Solution: requĂȘtes de Postman vers LocalStack.
Les API des services AWS sont documentĂ©es, ce qui permet de faire des requĂȘtes sans SDK.
Si le service écrit dans la file, nous la lisons et vérifions le contenu du message.
Si le service envoie des messages à SNS, à l'étape de préparation, LocalStack crée également une file et s'abonne à ce sujet SNS. Tout cela revient à ce qui est décrit ci-dessus.
Si le service doit lire un message de la file, à l'étape précédente du test, nous écrivons ce message dans la file.
Test des requĂȘtes HTTP sortantes du microservice testĂ©
Certains services fonctionnent via HTTP avec autre chose qu'AWS, et certaines fonctions AWS ne sont pas mises en Ćuvre dans LocalStack.
Solution: dans ces cas, cela peut ĂȘtre utile , qui a une image prĂȘte dans . Les requĂȘtes et les rĂ©ponses attendues sont configurĂ©es par une requĂȘte HTTP. L'API est documentĂ©e, donc nous faisons des requĂȘtes depuis Postman.
Test de l'authentification et de l'autorisation OAuth
Nous utilisons OAuth et . Pour le test, un fournisseur OAuth dont nous pouvons exécuter localement est nécessaire.
Toute interaction du service avec le fournisseur OAuth se rĂ©sume Ă deux requĂȘtes : d'abord, la configuration est demandĂ©e /.well-known/openid-configuration, puis la clĂ© publique (JWKS) est demandĂ©e Ă l'adresse indiquĂ©e dans la configuration. Tout cela est du contenu statique.
Solution: notre fournisseur OAuth de test est un serveur de contenu statique avec deux fichiers dessus. Le token a été généré une seule fois et a été commis dans Git.
Particularités du test de SignalR
Postman ne fonctionne pas avec les WebSockets. Un outil spécial a été créé pour tester SignalR.
Le client de SignalR peut ĂȘtre autre chose qu'un navigateur. Il existe une bibliothĂšque cliente pour .NET Core. Le client, Ă©crit en .NET Core, Ă©tablit une connexion, passe l'authentification et attend une sĂ©quence de messages spĂ©cifique. Si un message inattendu est reçu ou si la connexion est interrompue, le client se termine avec le code 1. Lors de la rĂ©ception du dernier message attendu, il se termine avec le code 0.
Newman fonctionne en mĂȘme temps que le client. Plusieurs clients sont lancĂ©s pour vĂ©rifier que les messages sont livrĂ©s Ă tous ceux qui en ont besoin.

Pour dĂ©marrer plusieurs clients, on utilise l'option âscale dans la ligne de commande docker-compose.
Avant de lancer Postman, le script attend que tous les clients établissent une connexion.
Nous avons déjà rencontré le problÚme d'attente de connexion. Mais là , il s'agissait de serveurs, alors qu'ici, c'est un client. Un autre approche est nécessaire.
Solution: le client dans le conteneur utilise le mécanisme , pour informer le script sur l'hÎte de son statut. Le client crée un fichier à un certain chemin, disons, /healthcheck, dÚs que la connexion est établie. Le script HealthCheck dans le fichier docker ressemble à ceci:
HEALTHCHECK --interval=3s CMD if [ ! -e /healthcheck ]; then false; fiCommande docker inspect affiche pour le conteneur l'état normal, l'état de santé et le code de sortie.
AprÚs la fin de Newman, le script vérifie que tous les conteneurs avec le client se sont terminés, et ce, avec le code 0.
Le bonheur existe
AprÚs avoir surmonté les complexités décrites ci-dessus, nous avons un ensemble de tests fonctionnant de maniÚre stable. Dans ces tests, chaque service fonctionne comme un tout, interagit avec la base de données et avec Amazon LocalStack.
Ces tests protÚgent l'équipe de 30+ développeurs des erreurs dans une application avec une interaction complexe entre 10+ microservices lors de fréquents déploiements.
Source : habr.com
