Automatisation des tests End-2-End d'un système d'information intégré. Partie 2. Technique

Avec cet article, nous continuons une série de publications sur la façon dont nous avons automatisé dans l'un des principaux projets LANIT le processus automatique de test manuel (ci-après dénommé autotests) d'un grand système d'information (ci-après dénommé Systèmes) et ce qui en est résulté.

La deuxième partie de la publication s'adresse principalement aux leaders des groupes d'automatisation UI end-2-end testing et leader de l'automatisation des tests. Ils y trouveront des recettes spécifiques pour l'organisation architecturale du code et du déploiement, qui soutiennent le développement en masse parallèle de grands groupes de tests face à la variabilité constante des spécifications de test. Cette partie contient la liste complète des fonctions nécessaires aux tests d'interface utilisateur avec quelques détails d'implémentation, ainsi qu'une liste de surprises que vous pourriez rencontrer.

Vous trouverez ici la partie 1. (Pourquoi avons-nous eu besoin d'automatisation. Organisation du processus de développement et de gestion. Organisation de l'utilisation)

Source

Pile d'architecture et de technologie


Structure générale du système et de son environnement - Conception de haut niveau


Les principales technologies et bibliothèques utilisées dans le projet:

  • JavaSE8 & maven & JUnit - pile de développement;
  • Selenium - une bibliothèque pour automatiser les actions d'un navigateur Web;
  • Selenide - un module complémentaire pour Selenium, qui possède une élégante API qui simplifie considérablement l'interaction avec le navigateur et les éléments de page;
  • Selenoid & GGR - implémentation de Selenium Grid et d'un équilibreur de charge pour exécuter des tests sur un serveur CI + conteneurs préconfigurés avec navigateurs;
  • Yandex Allure - pour les rapports sur le serveur CI.

Un diagramme général des composants des autotests et de l'infrastructure Selenoid est présenté dans les diagrammes ci-dessous, avec des commentaires explicatifs:

Cadre d'autotests
Application pour l'automatisation de la régression de l'interface utilisateur.
Livré en code source. Utilise JUnit4

run.properties
Fichier de configuration pour exécuter les autotests. Définit le nom conditionnel du stand utilisé et le type d'exécution - local ou via des conteneurs externes et d'autres variables.

Plugin Allure
Un fichier exécutable spécial qui s'installe sur un serveur Bamboo.
Crée un rapport HTML de test accessible via un serveur Bamboo.

Rapport de test
Rapport de test HTML disponible via le serveur Bamboo.
Il est stocké sur le serveur Bamboo dans les résultats du plan dans un onglet séparé.

Bambou
Fournit le lancement des tests d'intégration en mode automatique et manuel.
Stocke les rapports de test au format Allure.

ggr-server
Serveur-équilibreur de serveurs Selenoid.
Fournit l'équilibrage des demandes des autotests (RemoteWebDriver) à plusieurs instances de serveurs au sélénium.

Docker
Serveur Docker pour exécuter les conteneurs et les navigateurs du serveur Selenoid.

Serveur Selenoid
Serveur de test à distance
Fournit le lancement de tests dans des conteneurs Docker spécialisés à l'aide d'un navigateur "sans tête".
Effectue des tests en mode parallèle selon le nombre spécifié de threads simultanés.

Selenoid-ui
Interface utilisateur du serveur vers le serveur Selenoid.
Permet la surveillance à la volée de la progression des tests via VNC.

Sélénoïde-webdriver
Un conteneur spécialisé avec un navigateur sans tête pour exécuter des tests à distance.
Fourni à partir du référentiel Selenoid.

Gitlab
Stocke le code source de l'application Autotests.


Schéma de travail


Le diagramme suivant montre le schéma général du service AutoTests au niveau de conception de haut niveau.


Installation et déploiement


Les autotests sont basés sur quatre serveurs, dont l'un est le serveur d'exécution, et les trois autres permettent le lancement de navigateurs sans tête dans des conteneurs. La puissance actuelle des autotests fournit 60 flux simultanés et peut être étendue si nécessaire.

Le diagramme de déploiement actuel est illustré dans le diagramme suivant. En général, si vous n'avez pas besoin de plus de 20 navigateurs simultanés (tests de threads), il est tout à fait possible de tout mettre sur un même serveur 12 noyaux + 24 RAM. Nous avons commencé avec cette configuration, mais au fur et à mesure que les exigences pour la puissance des autotests augmentaient, nous avons découvert empiriquement que la configuration la plus stable et la plus rentable est un serveur «typique» à 12 noyaux + 24 RAM.

D'après notre expérience, pour tester des systèmes avec une interface Web sur Angular, la configuration acceptable du conteneur avec le navigateur devrait être le plancher du noyau + Go de mémoire. Une configuration plus petite ralentit le navigateur et peut même entraîner des plantages non identifiables.


Structure et organisation du code


Avant de passer à la structure et à l'organisation du code, je vais à nouveau énumérer les politiques et les restrictions qui ont finalement déterminé l'orientation du développement de l'architecture:

  • Un grand nombre de scripts de test de bout en bout complexes. Haute intensité de développement;
  • volatilité élevée des scénarios de test (leur variabilité);
  • vitesse continue élevée de livraison de scénarios (développement et révision);
  • qualifications initiales des développeurs d'autotests;
  • Développement planifié élevé des développeurs.

Sur la base de ce qui précède, les principaux moteurs architecturaux sont les suivants:

  • assurer un code hautement structuré pour une lisibilité et une gérabilité faciles;
  • séparation et isolation maximales entre les implémentations d'applications de tests spécifiques et les classes transversales de fonctionnalités communes;
  • réduction maximale des dépendances requises pour faciliter le changement;
  • Réutilisation raisonnable du code au niveau des classes transversales avec une duplication de code possible acceptable au niveau des groupes de test indépendants (sous-systèmes fonctionnels) pour réduire les dépendances et fusionner les conflits au niveau du développement;
  • rejet de frameworks complexes tels que spring, aspectJ pour réduire le temps d '"entrée" des développeurs novices dans le projet.

En général, cette approche architecturale a permis de mettre en œuvre un développement rapide indépendant dans des conditions de forte variabilité des scénarios avec un processus pratiquement continu de livraison de nouveaux tests au productif. L'architecture et maintenant résister avec succès à la "charge et le raffinement" malgré le fait que le système a déjà mis en œuvre plus de 1 500 scénarios d'entreprise.

La structure générale du code et les descriptions des solutions spécifiques clés sont données ci-dessous.

Structure générale du code et modèles de développement


La structure générale de l'organisation du code est basée sur une architecture «en couches» (voir schéma), qui hérite généralement du modèle de page recommandé par les développeurs Selenium. Nous avons développé ce modèle en ajoutant un niveau d'éléments Web à la base et en mettant en évidence le niveau du scénario de test:

  • niveau de la classe de test
  • niveau du scénario de test
  • niveau de la page Web
  • niveau d'élément Web
  • cadre de test (illustré par une gradation dans le diagramme).

Chaque niveau de ce schéma a un ensemble spécifique de responsabilités et de fonctionnalités. L'intersection fonctionnelle entre les couches ou les dépendances illégitimes entre elles n'étaient pas autorisées et étaient la principale raison du retour de la validation pour révision dans le cadre d'une révision de code avant la demande de fusion.

De plus, une structure «en couches» a été corrigée dans la division du code en packages (voir schéma ci-dessous).

Un tel schéma a permis de diviser tous les sous-systèmes (dont il y avait beaucoup plus que dans le schéma) entre les développeurs et, par conséquent, a permis de se développer indépendamment avec un nombre pratiquement disparaissant de conflits de fusion.

Le diagramme ci-dessous montre la structure générale de l'implémentation de la classe de test et le schéma de distribution pour l'implémentation des packages de projet.



Niveau de classe de test


Le niveau de classe de test comprend une seule classe pour un test individuel particulier (le test est décrit dans le système de gestion de test comme une séquence linéaire de scripts de test). La classe de test est une classe Junit avec l'annotation @ Test de la méthode correspondante. En général, une classe de test implémente une seule méthode de test.

Chaque classe de test hérite d'une classe de base dont la tâche est d'initialiser tous les TestRules dans RuleChain, c'est-à-dire d'initialiser une infrastructure de test telle que l'initialisation d'un pilote Web, la définition de répertoires temporaires et d'autres gestionnaires d'utilisation à usage général.

La classe de test est chargée d'organiser l'exécution du scénario de test à travers:

  1. initialisation des données métiers de test (scène de test),
  2. appel séquentiel de scripts de test individuels (Actions) selon le scénario requis avec le transfert des données de test initialisées à partir de l'étape 2.

De plus, à l'étape 2, il devrait y avoir une séquence linéaire sans branches ni points de décision. Le modèle d'implémentation de classe est illustré ci-dessous.

Niveau du scénario de test


Le niveau de scénario de test est responsable de la mise en œuvre de scénarios de test spécifiques, comme décrit. Un scénario de test dans ce contexte est une séquence d'opérations effectuées par un utilisateur sur un système via une interaction avec des éléments sur les pages Web de l'application.

L'objectif principal de la classe de cas de test:

  • exécuter la séquence donnée du script de test en accédant aux méthodes des classes inférieures de Page, en utilisant

    o données de scène de test transmises,
    o les données reçues des pages Web (des classes de pages);
  • Effectuez les tests commerciaux requis pour la réussite des tests dans le contexte du modèle commercial et de la scène de test. En d'autres termes, la classe de cas de test NE vérifie PAS le balisage, la disponibilité et l'accessibilité des éléments, mais fonctionne sur le modèle commercial de la scène de test, effectuant en fait des tests commerciaux.

La classe de cas de test est organisée comme une "interface fonctionnelle":

  • contient une seule méthode publique "apply" (@ Step), qui:

    o fournit l'implémentation du script comme un appel à une séquence "d'actions" (méthodes fournies par les classes Page),
    o accepte tous les objets métier nécessaires en entrée, alors qu'il NE CRÉE PAS lui-même d'objets métier et N'interagit PAS directement avec autre chose que les classes de page;
  • contient X méthodes privées (@ Step), chacune mettant en œuvre une étape distincte spécifique du script de test (comme décrit dans TMS - Test Management System);
  • N'interagit pas (n'appelle pas de méthodes) avec d'autres activités, même les activités d'un sous-système similaire;
  • n'accepte pas d'entrée et ne fonctionne pas sur les données de connexion. En d'autres termes, ne sait rien des rôles à partir desquels il est lancé.

Cette organisation de classe vous permet de:

  • Fournir un rapport d'auto-documentation. Chaque méthode correspond à un point spécifique de la spécification de test et est annotée par l'annotation d'allure @ Step;
  • réutiliser les classes de script de test dans différents tests, car le script de test 1) ne crée pas de scène de test et 2) ne dépend pas de la connexion de l'utilisateur (l'opération de connexion à la connexion est effectuée au niveau de la classe de test) 3), le test ne dépend pas d'autres classes scripting.

Niveau de la page Web


Le niveau de page Web est un modèle de page classique pour les tests dans Selenium. La classe de page contient des définitions d'éléments Web spécifiques et un ensemble de méthodes publiques pour effectuer certaines actions de groupe dans le contexte des étapes du scénario de test.

La classe de page est directement responsable de la saisie des données commerciales dans des éléments d'interface spécifiques, de la collaboration avec le pilote Web (lancement de JS, par exemple) et, en conséquence, du test des formats, de la vérification de la mise en page et de la structure de la page Web pour les vérifications de base telles que: l'élément n'est pas trouvé / non disponible et l'article ne contient pas la valeur requise.

La classe de page n'inclut pas non plus et ne peut pas accéder à d'autres pages et à leurs méthodes. De plus, les classes de pages n'effectuent pas de vérifications commerciales, se limitant uniquement aux vérifications structurelles au niveau des éléments Web dans le cas général, fournissant des données de niveau «jusqu'au» script de test reçues des pages.

Niveau d'élément Web


La couche d'éléments Web comprend une bibliothèque d'éléments finis qui peuvent se trouver sur une page Web. Il comprend à la fois des primitives spécifiques et des conglomérats plus complexes, constitués de plusieurs éléments que nous appelons widgets. Des exemples de widgets peuvent être des constructions telles que des «pajinators», des menus globaux, divers cadres et fenêtres modales, des éléments complexes tels que YandexMap ou un lecteur Youtube. Les éléments Web organisent la composition avec des classes de pages spécifiques et ne savent rien des autres éléments.

En général, si le projet a une identification globale unique de tous les éléments d'interface avec leur ID, il est logique d'organiser le niveau des éléments Web en tant que bibliothèque globale avec demande d'éléments spécifiques par leur ID via les classes de configuration d'usine ou \ xml dans la bibliothèque Spring. Mais ce n'est pas possible dans tous les projets.

Cadre de test


Le concept de développement d'autotests, tel qu'illustré dans le diagramme ci-dessus, est basé sur l'idée d'un cadre dans lequel un ensemble de fonctions système est fourni pour tous les autotests - ils sont parfaitement intégrés et permettent aux développeurs d'autotests de se concentrer sur des problèmes spécifiques de mise en œuvre commerciale des classes de test.

Le cadre comprend les blocs fonctionnels suivants:

  • Règles - initialisation et finalisation des composants de l'infrastructure de test comme l'initialisation de WebDriver et la réception d'un test vidéo (décrit plus en détail ci-dessous);
  • WebDriverHandlers - fonctions d'assistance pour travailler avec un pilote Web comme exécuter Java Script ou accéder aux journaux du navigateur. Implémenté comme un ensemble de méthodes statiques sans état;
  • WebElements - une bibliothèque d'éléments Web typiques ou de leurs groupes, contient la fonctionnalité inter-fonctions requise et le comportement typique. Dans notre cas, cette fonctionnalité comprend une vérification éventuelle de la fin des opérations asynchrones du côté du navigateur Web. Implémenté en tant qu'extensions d'éléments Web à partir des bibliothèques Selenium et Selenide.

Initialisation de l'environnement de test. Les règles


La classe clé pour toutes les classes de test est BaseTest, dont toutes les classes de test sont héritées. La classe BaseTest définit le «runner» Junit des tests et la RuleChain utilisée, comme indiqué ci-dessous. L'accès des classes de test d'application aux fonctions fournies par les classes de règles s'effectue via les méthodes statiques des classes de règles en utilisant l'identifiant de thread «thread» comme clé.

Les détails de l'implémentation des classes de base pour le framework d'autotest seront présentés dans la prochaine partie de l'article: voir Partie 2-1. Implémentation de la classe de base pour tous les tests et JUnit ChainRule.

Documenter les résultats via les rapports Allure.


Pour des rapports détaillés sur les tests effectués, Allure Framework est utilisé, intégré à Bamboo via le plugin Allure . Cela permet aux consommateurs de tests spécifiques (équipe de tests fonctionnels) non seulement de recevoir des données sur le crash d'un test particulier, mais également de restaurer facilement et, si nécessaire, de répéter un test abandonné en mode manuel.

Pour documenter le rapport de test, les fonctionnalités suivantes sont utilisées:

  • Annotations Allure et Junit pour le balisage du rapport par les étapes du script de test, ainsi qu'une description statique des métadonnées du test;
  • Pièces jointes Allure pour joindre au rapport des informations supplémentaires telles qu'un test vidéo, une capture d'écran, les résultats de diagnostics supplémentaires des raisons de l'accident, les journaux du navigateur Web téléchargés vers / depuis le navigateur de fichiers.

Les annotations Allure et Junit suivantes sont utilisées pour baliser le rapport.

  • Au niveau de la classe de test:

    o @ Feature - le nom du sous-système fonctionnel auquel appartient le test;
    o @ Story - le nom d'un cas de test spécifique;
    o @ Owner - le nom du développeur qui a apporté les dernières modifications au test;
    o @ TmsLink - lien vers la description du test dans le «système de gestion des tests» utilisé.
  • Au niveau de la méthode de test (@ Test) de la classe de test:

    o @ DisplayName - nom complet du script de test. Il diffère de @ Story en ce qu'il vous permet de partager les mêmes scripts, qui ne diffèrent que par la composition de la scène de test;
  • Au niveau des méthodes qui correspondent aux étapes spécifiques du scénario de test:

    o @ Step - le nom significatif de l'étape de test qui reflète l'essence commerciale de ce qui se passe.

La dénomination des étapes de test via @ Step vous permet de créer un rapport détaillé qui répète complètement toutes les étapes et les éléments décrits dans le scénario de test. Cela permet aux utilisateurs d'autotests de suivre facilement le déroulement du scénario de test et aide à déterminer le point d'incidence.

En général, l'utilisation d'Allure Framework s'est avérée très utile et facile, à l'exception de certaines fonctionnalités liées à l'énorme quantité de données générées dans notre cas pour un grand nombre de scripts de test longs et complexes (décrits plus loin dans la section "Rétrospective").

Qu'est-ce qui aurait pu être fait différemment tout de suite? Utiliser Spring Framework


Malgré le fait que lors de la mise en œuvre des autotests, nous avons intentionnellement refusé d'utiliser Spring Core, en général, je considère que son utilisation est justifiée et fonctionne. Le prototype implémenté a montré que l'utilisation de Spring Core fonctionne pour les tâches suivantes (bien que nous ne l'ayons pas testé complètement):

  • initialisation de la scène de test;
  • initialisation des pages Web et des éléments.

La seule caractéristique est la nécessité d'utiliser le contexte de portée "par défaut" du niveau prototype.

Initialisation de la scène de test. Dans les autotests, la scène de test est initialisée par la méthode classique de création d'instances de classe directement dans la classe de test via une simple fabrique de nouveaux objets ou d'objets. Ici, il est tout à fait raisonnable d'utiliser l'initialisation via des classes de configuration ou via des fichiers xml ou fichiers externes. Les avantages sont les suivants: cela 1) simplifie le code de révision, car les modifications apportées à la scène de test ne s'appliquent plus aux classes clés, 2) vous permet de connecter différentes scènes de test pour différents peuplements ou d'autres conditions. Maintenant, le point 2 n'est pas utilisé avec nous.

Initialisation des pages Web et des éléments. L'injection de classes de pages Web et d'éléments Web fonctionne grâce à l'initialisation paresseuse paresseuse des éléments Web en séléniure.

Ainsi, il devient possible de créer une bibliothèque d'éléments et de pages Web conformément à une certaine spécification globale de l'interface utilisateur et de recevoir dans le code des liens vers ces éléments non pas par le chemin absolu et l'id, mais par l'id du projet selon la spécification.

Je dois dire tout de suite que je n'ai pas testé cela très attentivement, en fait je me suis limité à un test d'implémentation des «pages» de la connexion et de la page «info» de l'utilisateur sur ce principe.

Rétrospective. Surprises


Ici, j'ai décrit les surprises inattendues qui se sont produites pendant le développement du projet, ainsi que quelques considérations sur ce qui aurait pu être mieux fait si «non».

Balisage frontal compatible avec le sélénium (angulaire)


L'une des surprises les plus sérieuses que nous avons rencontrées a été la mise en page «flottante», qui a conduit au fait qu'après la mise à jour des applications Angular, les tests ont échoué car Selenium n'a pas pu trouver les éléments car leur ID (id, class ou ng-model) ou des chemins (pour XPath). Cela a conduit à l'instabilité des tests et à l'ambiguïté des raisons de la chute.

Malheureusement, ce problème s'est avéré généralement insoluble. Nous l'avons contourné avec des mesures organisationnelles: 1) au début du test d'un nouveau candidat à la publication, tous les développeurs d'autotests se concentrent sur l'élimination rapide et le raffinement en termes de modification des valeurs des localisateurs d'éléments Web; 2) à la fin, nous sommes arrivés à utiliser XPath relatif, ce qui, hélas, n'améliore pas du tout les performances.

– , - .

«download»


«» :


.

. , , . , .


. , «» . , , , , , - ( ) - .

, , , . .

, . «» , , , .


. , . (~ 1000 ) 6 .

Selenoid-ggr . junit 20 ( Selenoid-) 60 ( ), . «» , 60 .

, , , 3-4 , preQA-. . , , AWS c , Selenoid- , AWS Selenoid, in/out .
, , E2E . preQA.

«»


, 60 60 .

«Timeline» « » ( ). , .


Il y avait deux raisons. La «prise» de départ a été provoquée par l'appel massif au service d'autorisation et au compte personnel. Tous les navigateurs commencent simultanément à se connecter et surchargent le service côté banc d'essai.

Les «colonnes» suivantes se sont révélées être liées au fait que nos classes de test sont situées dans des dossiers par sous-système et junit les a exécutées presque simultanément, chargeant ainsi un sous-système fonctionnel spécifique du stand et dépassant la limite de performance de la configuration du stand.

Nous avons résolu le premier problème en encapsulant la méthode du script de connexion dans l'équilibreur, ce qui limitait le nombre maximal d'opérations de connexion à une constante donnée.

@Step(":    ")     public void apply(User user) {     try {         LoadCounter.get(); //   .              try {                 applyLogin(user); //          } catch (Throwable t) {             throw t; //                   } finally {             LoadCounter.release(); //            }         } catch (LoadCounterException e) { //                  throw new StandRuntimeException("Can not get loadcounter for Login action.", e);     }     } 

Le deuxième problème a été résolu en activant l'option d'exécution de test aléatoire, que le plugin junit maven possède.

    <plugin>               <groupId>org.apache.maven.plugins</groupId>                <artifactId>maven-surefire-plugin</artifactId>         <version>….</version>            <configuration>                <!-- Defines the order the tests will be run in. Supported values are "alphabetical",                     "reversealphabetical", "random","hourly", "failedfirst", "balanced" and "filesystem". -->                <runOrder>random</runOrder>                … 

Il convient de noter que ces problèmes étaient liés au fait que nous n’avions pas eu l’occasion d’augmenter les performances du banc d’essai en raison de ressources limitées, et aussi parce que ces ressources seraient inutilisées la plupart du temps.

En général, il s'est avéré que le système d'autotest intégré de l'interface utilisateur Web E2E pouvait très bien être utilisé pour tester la charge de substitution et contrôler les performances et la stabilité du système testé dans son ensemble.

Charge élevée sur le serveur Bamboo et son stockage


Si vous avez de nombreux tests avec un grand nombre d'opérations (par exemple, le nombre d'opérations enregistrées via l' étape est de 200 à 2000 avec le nombre de tests environ 1300), alors le volume du rapport Allure devient plus que significatif. Si vous ajoutez ici également diverses pièces jointes telles que des captures d'écran et des fichiers téléchargés / téléchargés, le volume atteint déjà des centaines de mégaoctets. Par exemple, pour une régression nocturne de 900 tests, la quantité de données téléchargées sur Bamboo Allure à elle seule est d'environ 600 Mo.

Il est clair que les ingénieurs DevOps ne sont pas enthousiasmés par une telle intensité de consommation d'espace disque et expriment activement leur mécontentement, en particulier en ce qui concerne la nécessité de stocker les données pendant au moins un an.

Nous voyons un moyen de sortir de cette situation en utilisant un serveur externe pour stocker et traiter des rapports, par exemple, comme Allure Enterprise. Ce serveur est déjà payé, mais nous permet de résoudre ce problème. Nous travaillons actuellement sur les tests et l'intégration d'Allure Enterprise dans notre projet.

À suivre


Dans le prochain article, mes collègues continueront l'histoire et décriront l'histoire fascinante des tests de services Web à l'aide de Smartbear SoapUI Pro. Les forces de deux ingénieurs en automatisation ont réussi à automatiser environ 500 scripts de test; à cette fin, j'ai dû implémenter un cadre supplémentaire qui étend considérablement la fonctionnalité des fonctions standard de l'interface utilisateur SOAP, mais plus à ce sujet plus tard.

Cet article a été écrit en collaboration avec le chef de projet et le propriétaire du produit kotalesssk .

Partie 1. Organisation et gestion. Pourquoi avons-nous eu besoin d'automatisation. Organisation du processus de développement et de gestion. Organisation d'utilisation
Partie 2. Technique. Architecture et pile technique. Détails de mise en œuvre et surprises techniques
Partie 2-1. Implémentation de la classe de base pour tous les tests et JUnit ChainRule
Partie 2-2. Implémentation du processus de téléchargement d'un fichier depuis un conteneur avec un navigateur vers un framework de test. Recherchez le nom du fichier téléchargé par le navigateur

Source: https://habr.com/ru/post/fr477044/


All Articles