Mise à l'échelle des tests Android à Odnoklassniki



salut! Je m'appelle Roman Ivanitsky, je travaille dans l'équipe d'automatisation des tests d'Odnoklassniki. OK est un énorme service avec plus de 70 millions d'utilisateurs. Si nous parlons d'appareils mobiles, la majorité utilise OK.RU sur les smartphones fonctionnant sous Android. Pour cette raison, nous sommes très sérieux au sujet du test de notre application Android. Dans cet article, je vais raconter l'histoire du développement des tests automatisés dans notre entreprise.

2012, Odnoklassniki, la société connaît une augmentation active du nombre d'utilisateurs et une augmentation du nombre de fonctionnalités utilisateur. Afin de satisfaire les objectifs commerciaux, il était nécessaire de raccourcir le cycle de publication, mais cela a été entravé par le fait que toutes les fonctionnalités ont été testées manuellement. La solution à ce problème est venue d'elle-même - nous avons besoin d'autotests. Ainsi, en 2012 à Odnoklassniki une équipe d'automatisation des tests est apparue, et la première étape a été de commencer à écrire des tests.

Un peu d'histoire


Les premiers autotests à Odnoklassniki ont été écrits en sélénium, pour leur lancement, ils ont soulevé Jenkins, Selenium Grid avec Selenium Hub et un ensemble de sélénium Node.

Solution rapide, démarrage rapide, profit rapide - parfait.

Au fil du temps, le nombre de tests a augmenté et des services auxiliaires sont apparus - par exemple, services de lancement, service de rapport, service de données de test. À la fin de 2014, nous avions mille tests effectués en quinze à vingt minutes environ. Cela ne nous convenait pas, car il était clair que le nombre de tests augmenterait, et avec lui le temps nécessaire pour les exécuter augmenterait.

À cette époque, l'infrastructure de tests automatisés ressemblait à ceci:



Cependant, avec une quantité de nœud de sélénium supérieure ou égale à 200, le concentrateur n'a pas pu faire face à la charge. Maintenant, ce problème a déjà été étudié, et c'est pourquoi des outils tels que le Zalenium ou le Selenoid préféré de tous sont apparus. Mais en 2014, il n'y avait pas de solution standard, nous avons donc décidé de créer la notre.

Défini les exigences minimales auxquelles le service doit répondre:

  1. Évolutivité. Nous ne voulons pas dépendre des limites du Selenium Hub.
  2. La stabilité. En 2014, le Selenium Hub n'était pas réputé pour son fonctionnement stable.
  3. Tolérance aux pannes. Nous devons pouvoir poursuivre le processus de test en cas de panne du centre de données ou de l'un des serveurs.

Ainsi, notre solution pour la mise à l'échelle de Selenium Grid est apparue, composée d'un coordinateur et de gestionnaires de nœuds, extérieurement très similaire à la grille de sélénium standard, mais avec ses propres fonctionnalités. Ces caractéristiques seront discutées plus loin.

Coordinateur




En fait, c'est un courtier en ressources (les ressources sont comprises comme des navigateurs). Il dispose d'une API externe à travers laquelle les tests envoient des demandes de ressources. Ces requêtes sont enregistrées dans la base de données en tant que tâches à exécuter. Le coordinateur sait tout sur la configuration de notre cluster - quels gestionnaires de nœuds existent, quels types de ressources ces gestionnaires de nœuds peuvent fournir, le nombre total de ressources, combien de ressources sont actuellement impliquées dans les tâches. Dans le même temps, il surveille les ressources - activité, stabilité et, dans ce cas, informe les responsables.

Une caractéristique du coordinateur est qu'il intègre tous les gestionnaires de nœuds dans ce qu'on appelle des batteries de serveurs.

Voilà à quoi ressemble la ferme. Plus de la moitié des ressources sont utilisées et tous les nœuds en ligne:



Vous pouvez également afficher les nœuds hors ligne ou les entrer en rotation d'un certain pourcentage, cela est nécessaire s'il devient nécessaire de réduire la charge sur un nœud particulier.

Chaque ferme peut être combinée avec d'autres dans une unité logique, que nous appelons un service. En même temps, une ferme peut être incluse dans plusieurs services différents. Premièrement, il permet de fixer des limites et de hiérarchiser les ressources utilisées par chaque service spécifique. Deuxièmement, il vous permet de gérer facilement la configuration - nous avons la possibilité d'ajouter le nombre de gestionnaires de nœuds dans le service à la volée, ou vice versa pour les supprimer de la batterie de serveurs afin de pouvoir interagir avec ces gestionnaires de nœuds, par exemple, configurer ou mettre à jour, etc. .



L'API du coordinateur est assez simple: il est possible de demander le service pour la quantité actuelle de ressources utilisées, d'obtenir sa limite et de démarrer ou d'arrêter une ressource.

Gestionnaire de nœuds


Il s'agit d'un service qui peut bien faire deux choses: recevoir des tâches du coordinateur et lancer certaines ressources à la demande. Par défaut, il est conçu pour que chaque lancement de la ressource soit isolé, c'est-à-dire qu'aucun des lancements précédents ne peut affecter le lancement des tests suivants. En réponse, le coordinateur utilise un tas d'hôtes et un ensemble de ports surélevés. Par exemple, l'hôte sur lequel le serveur Selenium a été lancé et son port.



Sur l'hôte, cela ressemble à ceci: le service Node Manager est en cours d'exécution et il gère l'ensemble du cycle de vie des ressources. Il récupère les navigateurs, les complète, s'assure qu'ils ne sont pas oubliés de se fermer. Pour garantir l'isolement les uns des autres, tout cela se produit au nom de l'utilisateur du service.

Interaction


Le test interagit avec l'infrastructure décrite ci-dessus comme suit: il adresse au coordinateur une demande pour les ressources nécessaires, le coordinateur enregistre cette tâche comme nécessitant une exécution.

Le gestionnaire de nœud, à son tour, se tourne vers le coordinateur de tâches. Ayant reçu la tâche, il démarre la ressource. Après cela, il envoie le résultat du lancement au coordinateur, les échecs de démarrage sont également signalés au coordinateur. Le test reçoit le résultat de la demande de ressource et, s'il réussit, commence à travailler directement avec la ressource.



Les avantages de cette approche sont de réduire la charge du coordinateur en gagnant la capacité de travailler directement avec la ressource. Inconvénients - la nécessité de mettre en œuvre la logique d'interaction avec le coordinateur dans les cadres de test, mais pour nous, cela est acceptable.
Aujourd'hui, nous pouvons exécuter plus de 800 navigateurs en parallèle dans trois centres de données. Pour le coordinateur, ce n'est pas la limite.

La tolérance aux pannes est assurée par le lancement de plusieurs instances de coordinateur enroulées derrière le pare-feu DNS dans différents centres de données. Cela garantit l'accès à l'instance de travail en cas de problème avec le centre de données ou le serveur.

En conséquence, nous avons obtenu une solution qui répondait à toutes les exigences initialement définies. Il fonctionne régulièrement depuis 2015 et a prouvé son efficacité.

Android


En ce qui concerne les tests sur Android, il existe généralement deux approches principales. La première consiste à utiliser WebDriver - c'est ainsi que Selendroid et Appium fonctionnent. Le second - en travaillant avec des outils natifs, a donc implémenté Robotium, UI Automator ou Espresso.

Les similitudes fondamentales entre ces approches sont d'obtenir l'appareil et le navigateur.

Il y a beaucoup plus de différences, la principale est la nécessité d'installer l'APK testé, avec lequel nous prendrons des artefacts sous forme de journaux, de captures d'écran, etc. et aussi, le fait que les tests sont effectués sur l'appareil lui-même, et non sur le CI.

En 2015, Odnoklassniki a commencé à couvrir son application Android avec des autotests. Nous avons sélectionné une machine Linux, connecté un véritable appareil via USB et commencé à écrire des tests sur Robotium. Cette solution simple vous a permis d'obtenir rapidement des résultats.

Le temps a passé, le nombre de tests et le nombre d'appareils ont augmenté. Pour résoudre les tâches de gestion, Device Manager a été créé - un wrapper sur les commandes adb (Android Debug Bridge), qui permet à l'interface http api de les exécuter.

Voici à quoi ressemblait la première API pour Device Manager - avec son aide, vous pouvez obtenir une liste de périphériques, installer / désinstaller des APK, exécuter des tests et obtenir des résultats.



Cependant, nous avons remarqué que les résultats des tests se dégradent au démarrage sur le serveur ADB auquel plusieurs périphériques sont connectés. La solution qui nous a aidés à améliorer la stabilité a été trouvée en isolant chaque serveur ADB à l'aide de Docker.

La ferme est prête - vous pouvez connecter des téléphones.



Beaucoup connaissent cette image. J'ai entendu dire que si vous êtes engagé dans des fermes Android, vous êtes comme en enfer tous les jours.



Un émulateur Android est venu à notre aide. Son utilisation était due à deux facteurs: d'une part, à l'époque, il avait déjà atteint le niveau de stabilité nécessaire, et d'autre part, nous n'avions aucune caractéristique qui dépendrait spécifiquement du fer dans nos tests. De plus, cet émulateur était bien projeté sur l'infrastructure existante à l'époque. L'étape suivante consistait à apprendre au gestionnaire de nœuds à lancer de nouveaux types de ressources.

Que faut-il pour exécuter l'émulateur Android?

Tout d'abord, vous avez besoin d'un SDK Android avec un ensemble d'utilitaires.

Ensuite, vous devez créer AVD - Appareil virtuel Android - c'est ainsi que votre émulateur Android sera organisé - quelle architecture il aura, combien de cœurs il utilisera, si les services Google seront disponibles, etc.



Après cela, vous devez sélectionner le nom de l'AVD créé, définir les paramètres, par exemple, transférer le port sur lequel ADB sera lancé et démarrer.

Cependant, il y a une particularité dans un tel schéma - le système vous permet d'exécuter un seul émulateur d'instance sur un AVD spécifique.

La solution à ce problème était de créer un AVD de base, qui était stocké en mémoire, ce qui permettait de le copier ailleurs. Lors du lancement de l'émulateur Android, l'AVD de base a été copié dans un répertoire temporaire mappé en mémoire, après quoi il a démarré. Un tel système a fonctionné rapidement, mais était lourd. À ce jour, ce problème a été résolu par l'option en lecture seule, qui vous permet d'exécuter des émulateurs Android en quantités illimitées à partir d'un seul AVD

Performance


Sur la base des résultats du travail avec AVD, nous avons développé plusieurs recommandations internes:

  1. 86 , ARM . dev/kvm Linux HAXM- Mac Windows
  2. GPU- . , . , , , Android-
  3. .
  4. , localhost,

Quant aux images Docker pour les tests sur Android, je veux souligner Agoda et Selenoid, elles utilisent au maximum les capacités des émulateurs Android.

La différence entre eux est que dans le sélénoïde par défaut , Appium , Agoda et l'émulateur "propre" sont utilisés. De plus, Selenoid a plus de soutien communautaire.

Fin 2018, CloudNode-Manager a été créé, il contacte le coordinateur, reçoit les tâches et se lance à l'aide de commandes dans le cloud. Au lieu de machines à repasser, ce service utilise les ressources d' un cloud - le cloud privé d'Odnoklassniki.

Nous avons réussi à atteindre l'échelle en enseignant à DeviceManager comment travailler avec le coordinateur. Pour ce faire, j'ai dû changer l'API du gestionnaire de périphériques pour ajouter la possibilité de demander un type de périphérique (virtuel / réel).

C'est ce qui se produit si vous essayez d'exécuter ADB Install sur 250 émulateurs à partir d'une seule machine.



Les préposés ont immédiatement réagi à cela et ont déclenché un incident - la machine a chargé l'interface réseau gigabit avec le trafic sortant. Cette complexité a été résolue en augmentant le débit sur le serveur. Je ne peux pas dire que ce problème nous a causé beaucoup de problèmes, mais vous ne devez pas l’oublier.

Il semblerait que le succès soit le Devicemanager, coordinateur, scaling. Nous pouvons exécuter des tests sur toute la ferme. En principe, nous pouvons les exécuter à chaque demande de tirage et le développeur recevra rapidement des commentaires.



Mais tout n'est pas si rose. Vous avez peut-être remarqué que jusqu'à présent, rien n'a été dit sur la qualité des tests.



Voilà à quoi ressemblaient nos lancements. Et le plus intéressant, c'est qu'entre les lancements, des tests complètement différents pourraient tomber. Ce sont des chutes instables. Et ni moi, ni les développeurs, ni les testeurs n'ont fait confiance à ces résultats.

Comment avons-nous réglé ce problème? Ils ont juste tout copié, de Robotium à Espresso, et c'est devenu bon ... En fait, non.

Pour résoudre ce problème, nous avons non seulement tout réécrit sur Espresso, mais nous avons également commencé à utiliser l'API pour toutes sortes d'actions telles que le téléchargement de photos, la création de messages, l'ajout à des amis, etc., une connexion rapide, des diplinks utilisés qui vous permettaient d'accéder directement à l'écran souhaité et, bien sûr, nous avons analysé tous les cas de test.

Maintenant, les exécutions de test ressemblent à ceci:



vous pouvez remarquer que les tests rouges restent, mais il est important de se rappeler que ce sont des tests de bout en bout qui s'exécutent en production. Nous avons une limite sur le nombre de tests qui peuvent tomber dans la branche principale de l'application.

Nous avons maintenant des tests et une mise à l'échelle stables. Cependant, l'infrastructure de test est toujours fortement liée aux tests. Dans le même temps, en raison de l'attente de tests de bout en bout, CI est occupé et d'autres assemblys peuvent faire la queue, en attendant les agents libres. De plus, il n'y a pas de schéma clair pour travailler avec des démarrages parallèles.

Les raisons mentionnées ci-dessus sont devenues l'impulsion pour le développement de QueueRunner - un service qui vous permet d'exécuter des tests de manière asynchrone sans bloquer le CI. Pour travailler, il a besoin d'un test et d'un test APK, ainsi que d'un ensemble de tests. Ayant reçu les données nécessaires, il pourra organiser des runs de runs dans la file d'attente, allouer et libérer les ressources nécessaires. QueueRunner télécharge les résultats de l'exécution sur Jira et Stash, et les envoie également par courrier et dans le messager.

QueueRunner a un flux de test - il surveille le cycle de vie du test. Le flux par défaut que nous utilisons maintenant se compose de cinq étapes:

  1. Appareil récepteur. À ce stade, le Devicemanager demande via le coordinateur un appareil réel ou virtuel.
  2. . APK , – , .
  3. ,

En conséquence, cinq étapes simples constituent l'ensemble du cycle de vie du test dans notre service.



Quels avantages QueueRunner nous a-t-il apporté? Tout d'abord, il utilise toutes les ressources possibles au maximum - il peut être adapté à l'ensemble de la batterie de serveurs et obtenir rapidement des résultats. Deuxièmement, avec le bonus, nous avons eu la possibilité de contrôler la séquence des tests. Par exemple, nous pouvons exécuter les tests les plus longs ou les plus problématiques au début et ainsi réduire le temps d'attente pour qu'ils s'exécutent.

QueueRunner vous permet également de créer des plateaux intelligents. Nous stockons toutes les données dans la base de données, donc à tout moment nous pouvons voir l'historique du test. Par exemple, il est possible d'examiner le ratio de réussite et d'échec du test et de décider si, en principe, cela vaut la peine de recommencer le test.

QueueRunner et Devicemanager nous ont donné la possibilité de nous adapter à la quantité de ressources. Maintenant, nous pouvons évoluer sur l'ensemble de la batterie de serveurs, grâce à l'utilisation d'émulateurs, c'est-à-dire qu'un nombre presque illimité d'appareils virtuels nous a donné la possibilité d'exécuter beaucoup plus de tests, mais si pour une raison quelconque les ressources ne sont pas disponibles, le service attendra leur retour et il n'y aura aucune perte de lancements. Nous utilisons uniquement les ressources à notre disposition, en conséquence, après un certain temps, les résultats seront toujours obtenus et en même temps, l'IC ne sera pas bloqué. Et surtout, l'infrastructure de test et les tests sont désormais séparés.
Maintenant, pour exécuter des tests sur Android, il vous suffit de nous donner un APK de test et une liste de tests.

Nous avons parcouru un long chemin depuis la ferme Selenium sur machines virtuelles jusqu'au lancement de tests Android dans le cloud. Cependant, ce chemin n'est pas encore terminé.

Processus de développement


Voyons comment l'infrastructure de test est liée au processus de développement et comment les testeurs et les développeurs la voient.

Notre équipe Android utilise le GitFlow standard:



chaque fonctionnalité a sa propre branche. Le développement principal se déroule dans la branche develop. Un développeur qui décide de créer une nouvelle super fonctionnalité commence son développement dans sa propre branche, tandis que d'autres développeurs peuvent travailler dans d'autres branches en parallèle. Lorsqu'un développeur considère que le meilleur code idéalement beau au monde est prêt et doit être déployé auprès des utilisateurs le plus rapidement possible, il fait une demande d'extraction lors du développement, l'unité est automatiquement assemblée, des tests unitaires et des tests de composants sont exécutés. Simultanément, les fichiers APK sont assemblés, envoyés à QueueRunner et des tests de bout en bout sont exécutés. Après cela, les résultats de l'exécution des tests reviennent au développeur.

Cependant, il y a une forte probabilité qu'après la création de la branche de fonctionnalité dans develop, il y ait eu de nombreux commits. Cela signifie que développer peut ne plus être ce qu'il était. Par conséquent, la pré-fusion se produit en premier - nous fusionnons le développement dans la branche de fonctionnalité actuelle, et c'est sur cet état prématuré que nous faisons l'assemblage, les tests unitaires, les tests de composants, de bout en bout, et sur la base de ces résultats, nous faisons un rapport. Ainsi, nous comprenons à quel point la fonctionnalité est fonctionnelle dans la version actuelle de develop et, si tout va bien, elle est envoyée aux utilisateurs.



Rapports


Voici à quoi ressemble le rapport Stash:



Notre bot écrit d'abord que les tests ont commencé, et quand ils réussissent, il met à jour le message et ajoute combien ont réussi, combien sont tombés, combien d'erreurs connues et combien de tests Flaky. Il écrit la même chose dans Jira et ajoute un lien vers une comparaison des lancements.

Voici à quoi ressemble la comparaison des deux lancements:



Ici, l'exécution en cours dans la branche de fonctionnalité est comparée à la dernière exécution du développement. Il contient des informations sur le nombre de tests en cours d'exécution, les problèmes de correspondance, les tests abandonnés et les tests Flaky instables qui étaient dans un état et passés à un autre.

Si au moins un test unitaire ou plus d'un certain seuil de tests de bout en bout tombe, la fusion sera bloquée.

Afin de comprendre si les tests tombent de manière stable, nous comparons les hachages des traces traces des chutes, avant qu'elles soient préalablement débarrassées des chiffres, il ne reste que les numéros de ligne. Si les hachages correspondent, alors c'est la même chute, s'ils sont différents, alors les chutes seront probablement différentes.

Sommaire


En conséquence, nous avons mis en œuvre une solution stable et tolérante aux pannes qui s'adapte bien à notre infrastructure. Ensuite, l'infrastructure résultante a été adaptée pour les tests Android. Nous avons été aidés à cet égard par le gestionnaire de périphériques, qui nous aide à travailler à la fois avec des périphériques réels et virtuels, ainsi qu'avec QueueRunner, qui nous a aidés à séparer l'infrastructure et les tests, et non à bloquer les CI pendant la durée des tests.

Cela ressemblait à la durée d'exécution du test pendant une semaine en 2016 - de cinquante minutes ou plus.



Voilà à quoi cela ressemble maintenant:



ce graphique montre les exécutions qui ont eu lieu sur 2 heures d'un jour ouvrable moyen. Le temps de course a été réduit à un maximum de 15 minutes, le nombre de courses a considérablement augmenté.

All Articles