Pour des tests stables de bout en bout (E2E), nous avons besoin d'un environnement aussi isolé que possible de l'extérieur.
Les tests floconneux sont des tests qui échouent pour des raisons sans rapport avec votre code. Ils rendent difficile l'utilisation d'E2E comme vérification fiable de l'exactitude de l'application. Dans les cas extrêmes, des tests floconneux apprendront à votre équipe à ignorer les résultats E2E. Cela peut tuer l'effort d'automatisation du contrôle qualité (QA).
L'approche présentée ici aborde deux grandes sources de problèmes potentiels dans vos exécutions de test :
Il existe d'autres sources de flakiness non affectées par cette approche :
Avant que mon équipe et moi n'effectuions mes tests sur un conteneur backend dédié, nous utilisions l'un de nos serveurs hors production. Cette approche convenait parfaitement au stade de l'expérimentation de notre solution E2E.
Lorsqu'il n'y avait que quelques tests, nous ne pouvions de toute façon pas utiliser les résultats des tests pour prendre des décisions.
Cependant, alors que nous continuions à ajouter plus de tests et à générer des temps d'exécution plus longs, cette approche a commencé à s'effondrer. Les principaux enjeux étaient les suivants :
Une solution à ce problème consistait à dédier un serveur principal et une base de données distincts pour chaque travail de test. Cette approche serait très difficile sans Docker.
Les conteneurs Docker sont un outil parfait pour créer un environnement confiné avec tout ce dont une application a besoin pour fonctionner :
Pour votre test, vous pouvez préparer un conteneur de base de données dédié fourni avec des données de test prévisibles. De cette manière, vous pourrez reproduire exactement le point de départ de chaque exécution E2E, ce qui rendra vos tests plus stables.
Vous pouvez utiliser différentes balises pour votre image Docker, pour la gestion des versions de la base de données de test. La même base de données de test peut également être utilisée dans un environnement de développement. Pour les tests manuels en développement, vous avez besoin d'exemples d'entités similaires à ceux des tests automatisés.
Si vous avez déjà utilisé Docker pour déployer votre backend, il sera assez facile de réutiliser la même image pour exécuter votre E2E. Dans mon équipe, nous déployons un backend sous forme de conteneurs et nous fournissons des URL de base de données et des informations d'identification sous forme de variables d'environnement.
La même version de conteneur peut être déployée en production ou utilisée en intégration continue (CI) pour exécuter des tests. Chaque environnement fournit les bonnes valeurs pour se connecter à la base de données.
En fonction de votre stratégie de déploiement, vous pouvez effectuer l'une des opérations suivantes :
Utilisez les conteneurs que vous créez dans le cadre de la version frontale.
Obtenez les fichiers compilés et assurez-vous qu'ils sont disponibles via HTTP pour les tests.
Dans notre cas, nous utilisons l'option 2 : nous déployons l'application sous forme de fichiers statiques, nous venons donc de créer un conteneur dédié pour servir les fichiers construits lors des exécutions de tâches E2E.
Nous utilisons GitLab comme plate-forme pour exécuter notre CI. Chaque travail dans GitLab est exécuté dans un conteneur avec une image de votre choix. En plus du conteneur principal, vous pouvez définir des services : Des conteneurs supplémentaires s'exécutant parallèlement à vos tests. La configuration est aussi simple que :
<job-name>: services: - name: <image> alias: <container-url>
Les options disponibles sont similaires à celles que vous avez dans Docker Compose, mais elles sont plus limitées.
Un "gotcha" dans la configuration GitLab consiste à définir la variable FF_NETWORK_PER_BUILD
sur 1 si vous souhaitez autoriser les services à s'accéder pendant l'exécution du test.
À un moment donné, nous exécutions tous les tests en parallèle dans un seul travail. À cette époque, il était nécessaire d'appliquer une isolation encore plus forte : chaque test utilisait le même backend et la même base de données.
Pour contourner ce problème, nous avons mis à jour nos tests pour qu'ils dépendent principalement des données aléatoires que nous injectons juste dans la section before
des tests. Cela permettait aux tests de s'exécuter sans être affectés par d'autres modifications se produisant dans d'autres threads.
Cette approche peut être un peu délicate au début, mais elle peut avoir du sens selon votre situation.
Même si nous commençons une nouvelle base de données pour chaque tâche de test, nous essayons toujours de faire en sorte que nos tests laissent l'application dans le même état qu'ils l'ont trouvée. C'est peut-être un peu un vestige de la période où nous faisions des tests dans un environnement partagé.
Ce n'est plus crucial, mais cela peut toujours aider lors du développement des tests dans les cas suivants :
Il existe des cas où le déplacement de services vers un conteneur n'est pas une option. Par exemple:
Dans les deux cas, pour isoler les exécutions de test, vous pouvez vous moquer des requêtes adressées à ces services. Cela empêchera les services externes imprévisibles d'affecter vos résultats de test. Un inconvénient de cette approche est que vos tests se déconnecteront du contexte dans lequel vos applications fonctionnent.
Avec des simulations en place, vos tests ne détectent pas les cas où des modifications de ces services affectent votre application.
Si vous souhaitez en savoir plus sur les tests ou d'autres sujets liés à la programmation, vous pouvez vous inscrire ici pour recevoir des mises à jour lorsque je publie du contenu connexe.