Skalieren von Android-Tests bei Odnoklassniki



Hallo! Mein Name ist Roman Ivanitsky, ich arbeite im Testautomatisierungsteam von Odnoklassniki. OK ist ein riesiger Dienst mit über 70 Millionen Benutzern. Wenn wir über mobile Geräte sprechen, verwendet die Mehrheit OK.RU auf Smartphones mit Android. Aus diesem Grund nehmen wir unsere Android-Anwendung sehr ernst. In diesem Artikel werde ich die Geschichte der Entwicklung automatisierter Tests in unserem Unternehmen erzählen.

2012, Odnoklassniki, verzeichnet das Unternehmen eine aktive Zunahme der Anzahl der Benutzer und eine Zunahme der Anzahl der Benutzerfunktionen. Um die Geschäftsziele zu erreichen, musste der Freigabezyklus verkürzt werden. Dies wurde jedoch durch die Tatsache behindert, dass alle Funktionen manuell getestet wurden. Die Lösung für dieses Problem kam von selbst - wir brauchen Autotests. So erschien 2012 in Odnoklassniki ein Testautomatisierungsteam, und der erste Schritt bestand darin, mit dem Schreiben von Tests zu beginnen.

Ein bisschen Geschichte


Die ersten Autotests in Odnoklassniki wurden in Selenium geschrieben. Für ihren Start wurden Jenkins, Selenium Grid mit Selenium Hub und eine Reihe von Seleniumknoten angehoben.

Schnelle Lösung, schneller Start, schneller Gewinn - perfekt.

Im Laufe der Zeit nahm die Anzahl der Tests zu, und es wurden Hilfsdienste angezeigt, z. B. Startdienste, Berichtsdienst, Testdatendienst. Bis Ende 2014 hatten wir tausend Tests, die in etwa fünfzehn bis zwanzig Minuten durchgeführt wurden. Dies passte nicht zu uns, da klar war, dass die Anzahl der Tests zunehmen würde und damit die Zeit, die für deren Durchführung benötigt wurde, zunehmen würde.

Zu diesem Zeitpunkt sah die automatisierte Testinfrastruktur folgendermaßen aus:



Mit einer Menge an Seleniumknoten größer oder gleich 200 konnte der Hub die Last jedoch nicht bewältigen. Jetzt wurde dieses Problem bereits untersucht, und deshalb tauchten Werkzeuge wie Zalenium oder jedermanns Lieblings-Selenoid auf. Da es 2014 keine Standardlösung gab, haben wir uns entschlossen, unsere eigene zu machen.

Definierte die Mindestanforderungen, die der Service erfüllen muss:

  1. Skalierbarkeit. Wir möchten uns nicht auf die Einschränkungen des Selenium Hub verlassen.
  2. Stabilität. Im Jahr 2014 war der Selenium Hub nicht für seinen stabilen Betrieb bekannt.
  3. Fehlertoleranz. Wir benötigen die Fähigkeit, den Testprozess im Falle eines Ausfalls des Rechenzentrums oder eines der Server fortzusetzen.

So erschien unsere Lösung zur Skalierung des Selenium-Gitters, bestehend aus einem Koordinator und Knoten-Managern, äußerlich dem Standard-Selenium-Gitter sehr ähnlich, jedoch mit eigenen Merkmalen. Diese Funktionen werden weiter erläutert.

Koordinator




Tatsächlich handelt es sich um einen Ressourcenbroker (Ressourcen werden als Browser verstanden). Es verfügt über eine externe API, über die Tests Ressourcenanforderungen senden. Diese Abfragen werden als auszuführende Aufgaben in der Datenbank gespeichert. Der Koordinator weiß alles über die Konfiguration unseres Clusters - welche Knotenmanager existieren, welche Arten von Ressourcen diese Knotenmanager bereitstellen können, die Gesamtzahl der Ressourcen, wie viele Ressourcen derzeit an Aufgaben beteiligt sind. Gleichzeitig überwacht er die Ressourcen - Aktivität, Stabilität und benachrichtigt in diesem Fall die Verantwortlichen.

Ein Merkmal des Koordinators ist, dass er alle Knotenmanager in sogenannte Farmen integriert.

So sieht die Farm aus. Mehr als die Hälfte der Ressourcen wird verwendet, und alle Knoten sind online:



Sie können Knoten auch offline anzeigen oder um einen bestimmten Prozentsatz in Rotation versetzen. Dies ist erforderlich, wenn die Belastung eines bestimmten Knotens verringert werden muss.

Jede Farm kann mit anderen in einer logischen Einheit kombiniert werden, die wir als Service bezeichnen. Gleichzeitig kann eine Farm in mehreren verschiedenen Diensten enthalten sein. Erstens ist es möglich, Grenzwerte festzulegen und die von den einzelnen Diensten verwendeten Ressourcen zu priorisieren. Zweitens können Sie die Konfiguration einfach verwalten. Wir haben die Möglichkeit, die Anzahl der Knotenmanager im Dienst im laufenden Betrieb hinzuzufügen oder sie umgekehrt aus der Farm zu entfernen, um mit diesen Knotenmanagern interagieren zu können, z. B. zu konfigurieren oder zu aktualisieren usw. .



Die API des Koordinators ist recht einfach: Es ist möglich, den Dienst für die aktuell verwendete Ressourcenmenge anzufordern, sein Limit zu ermitteln und eine Ressource zu starten oder zu stoppen.

Knotenmanager


Dies ist ein Dienst, der zwei Dinge gut kann - Aufgaben vom Koordinator erhalten und einige Ressourcen bei Bedarf starten. Standardmäßig ist es so konzipiert, dass jeder Start der Ressource isoliert ist, dh, keiner der vorherigen Starts kann den Start nachfolgender Tests beeinflussen. Als Antwort verwendet der Koordinator eine Reihe von Hosts und eine Reihe von erhöhten Ports. Zum Beispiel der Host, auf dem der Selenium-Server gestartet wurde, und sein Port.



Auf dem Host sieht es folgendermaßen aus: Der Node Manager-Dienst wird ausgeführt und verwaltet den gesamten Ressourcenlebenszyklus. Er nimmt Browser in die Hand, vervollständigt sie und stellt sicher, dass das Schließen nicht vergessen wird. Um die Isolation voneinander zu gewährleisten, geschieht dies alles im Auftrag des Dienstnutzers.

Interaktion


Der Test interagiert mit der oben beschriebenen Infrastruktur wie folgt: Er adressiert den Koordinator mit einer Anfrage nach den erforderlichen Ressourcen, der Koordinator speichert diese Aufgabe als ausführungsbedürftig.

Der Knotenmanager wendet sich wiederum an den Aufgabenkoordinator. Nachdem er die Aufgabe erhalten hat, startet er die Ressource. Danach sendet er das Ergebnis des Starts an den Koordinator. Fehlgeschlagene Starts werden ebenfalls an den Koordinator gemeldet. Der Test empfängt das Ergebnis der Ressourcenanforderung und beginnt bei Erfolg direkt mit der Ressource zu arbeiten.



Die Vorteile dieses Ansatzes bestehen darin, die Belastung des Koordinators zu verringern, indem die Fähigkeit erlangt wird, direkt mit der Ressource zu arbeiten. Nachteile - die Notwendigkeit, die Logik der Interaktion mit dem Koordinator innerhalb der Testrahmen zu implementieren, aber für uns ist dies akzeptabel.
Heute können wir in drei Rechenzentren mehr als 800 Browser parallel ausführen. Für den Koordinator ist dies nicht die Grenze.

Die Fehlertoleranz wird durch den Start mehrerer Koordinatorinstanzen sichergestellt, die in verschiedenen Rechenzentren hinter der DNS-Firewall aufgelöst werden. Dies garantiert den Zugriff auf die Arbeitsinstanz bei Problemen mit dem Rechenzentrum oder dem Server.

Als Ergebnis haben wir eine Lösung erhalten, die alle ursprünglich festgelegten Anforderungen erfüllt. Es ist seit 2015 stetig in Betrieb und hat seine Wirksamkeit bewiesen.

Android


Wenn es um das Testen auf Android geht, gibt es normalerweise zwei Hauptansätze. Das erste ist die Verwendung von WebDriver - so funktionieren Selendroid und Appium. Die zweite - bei der Arbeit mit nativen Tools - implementierte Robotium, UI Automator oder Espresso.

Die grundlegenden Ähnlichkeiten zwischen diesen Ansätzen bestehen darin, das Gerät und den Browser zu erhalten.

Es gibt viel mehr Unterschiede, der wichtigste ist die Notwendigkeit, die getestete APK zu installieren, mit der wir Artefakte in Form von Protokollen, Screenshots usw. aufnehmen. und auch die Tatsache, dass die Tests am Gerät selbst und nicht am CI durchgeführt werden.

Im Jahr 2015 begann Odnoklassniki, seine Android-Anwendung mit Autotests zu versehen. Wir haben einen Linux-Computer ausgewählt, ein echtes Gerät über USB angeschlossen und Tests auf Robotium geschrieben. Mit dieser einfachen Lösung konnten Sie schnell Ergebnisse erzielen.

Die Zeit verging, die Anzahl der Tests und die Anzahl der Geräte nahmen zu. Zur Lösung von Verwaltungsaufgaben wurde der Geräte-Manager erstellt - ein Wrapper-over- adb-Befehl (Android Debug Bridge), mit dem die http-API-Schnittstelle diese ausführen kann.

So sah die erste API für den Geräte-Manager aus - mit ihrer Hilfe konnten Sie eine Liste der Geräte abrufen, APKs installieren / deinstallieren, Tests ausführen und Ergebnisse abrufen.



Wir haben jedoch festgestellt, dass sich die Testergebnisse beim Start auf dem ADB-Server verschlechtern, an den mehr als ein Gerät angeschlossen ist. Die Lösung, die uns geholfen hat, die Stabilität zu verbessern, bestand darin, jeden ADB-Server mit Docker zu isolieren.

Die Farm ist fertig - Sie können Telefone anschließen.



Viele kennen dieses Bild. Ich habe gehört, wenn Sie in Android-Farmen tätig sind, sind Sie jeden Tag wie in der Hölle.



Ein Android-Emulator kam uns zu Hilfe. Seine Verwendung war auf zwei Faktoren zurückzuführen: Erstens hatte es zu diesem Zeitpunkt bereits das erforderliche Stabilitätsniveau erreicht, und zweitens hatten wir in unseren Tests keine Merkmale, die spezifisch vom Eisen abhängen würden. Darüber hinaus wurde dieser Emulator zu diesem Zeitpunkt gut auf die vorhandene Infrastruktur projiziert. Der nächste Schritt bestand darin, dem Knotenmanager beizubringen, neue Arten von Ressourcen zu starten.

Was ist erforderlich, um den Android-Emulator auszuführen?

Zunächst benötigen Sie ein Android SDK mit einer Reihe von Dienstprogrammen.

Dann müssen Sie AVD - Android Virtual Device - erstellen. So wird Ihr Android-Emulator organisiert - welche Architektur wird er haben, wie viele Kerne wird er verwenden, ob Google-Dienste verfügbar sein werden usw.



Danach müssen Sie den Namen der erstellten AVD auswählen, die Parameter festlegen, z. B. den Port übertragen, an dem ADB gestartet wird, und starten.

Ein solches Schema weist jedoch eine Besonderheit auf: Mit dem System können Sie nur einen Instanzemulator auf einer bestimmten AVD ausführen.

Die Lösung für dieses Problem bestand darin, eine grundlegende AVD zu erstellen, die im Speicher gespeichert war. Dadurch war es möglich, sie an eine andere Stelle zu kopieren. Während des Starts des Android-Emulators wurde die Basis-AVD in ein temporäres Verzeichnis kopiert, das dem Speicher zugeordnet ist, und anschließend gestartet. Ein solches Schema funktionierte schnell, war aber umständlich. Bisher wurde dieses Problem durch die schreibgeschützte Option behoben, mit der Sie Android-Emulatoren in unbegrenzten Mengen von einer AVD ausführen können

Performance


Basierend auf den Ergebnissen der Zusammenarbeit mit AVD haben wir mehrere interne Empfehlungen entwickelt:

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

Was die Docker-Images zum Testen auf Android betrifft, möchte ich Agoda und Selenoid hervorheben, sie nutzen die Fähigkeiten von Android-Emulatoren maximal.

Der Unterschied zwischen ihnen ist, dass in der Standardeinstellung Selenoid Appium , Agoda und den verwendeten "sauberen" Emulator haben. Darüber hinaus hat Selenoid mehr Community-Unterstützung.

Ende 2018 wurde CloudNode-Manager erstellt, der den Koordinator kontaktiert, Aufgaben empfängt und über Befehle in der Cloud gestartet wird. Anstelle von Eisenmaschinen nutzt dieser Service die Ressourcen einer Cloud - Odnoklassnikis eigene private Cloud.

Wir haben es geschafft, eine Skalierung zu erreichen, indem wir DeviceManager beigebracht haben, wie man mit dem Koordinator arbeitet. Dazu musste ich die Geräte-Manager-API ändern, um die Möglichkeit hinzuzufügen, einen Gerätetyp (virtuell / real) anzufordern.

Dies passiert, wenn Sie versuchen, ADB Install auf 250 Emulatoren von einem einzelnen Computer aus auszuführen.



Die Mitarbeiter reagierten sofort darauf und starteten einen Vorfall - der Computer lud die Gigabit-Netzwerkschnittstelle mit ausgehendem Datenverkehr. Diese Komplexität wurde durch Erhöhen des Durchsatzes auf dem Server behoben. Ich kann nicht sagen, dass dieses Problem uns viel Ärger bereitet hat, aber Sie sollten es nicht vergessen.

Es scheint, dass Erfolg der Devicemanager, Koordinator, Skalierung ist. Wir können Tests auf der gesamten Farm durchführen. Im Prinzip können wir sie bei jeder Pull-Anfrage ausführen, und der Entwickler erhält schnell Feedback.



Aber nicht alles ist so rosig. Möglicherweise haben Sie bemerkt, dass bisher nichts über die Qualität der Tests gesagt wurde.



So sahen unsere Starts aus. Und das Interessanteste ist, dass zwischen den Starts völlig unterschiedliche Tests fallen könnten. Dies waren instabile Stürze. Und weder ich noch die Entwickler noch die Tester vertrauten diesen Ergebnissen.

Wie sind wir mit diesem Problem umgegangen? Sie haben einfach alles von Robotium bis Espresso kopiert und es wurde gut ... Eigentlich nein.

Um dieses Problem zu lösen, haben wir nicht nur alles auf Espresso neu geschrieben, sondern auch die API für alle Arten von Aktionen wie das Hochladen von Fotos, das Erstellen von Posts, das Hinzufügen zu Freunden usw. verwendet. Wir haben uns schnell angemeldet und Diplinks verwendet , mit denen Sie direkt zum gewünschten Bildschirm gelangen können und natürlich haben wir alle Testfälle analysiert.

Jetzt sehen die Testläufe folgendermaßen aus:



Möglicherweise stellen Sie fest, dass die roten Tests erhalten bleiben. Beachten Sie jedoch, dass es sich um End-to-End-Tests handelt, die in der Produktion ausgeführt werden. Wir haben eine Begrenzung für die Anzahl der Tests, die in den Hauptzweig der Anwendung fallen können.

Jetzt haben wir stabile Tests und Skalierungen. Die Testinfrastruktur ist jedoch immer noch stark an die Tests gebunden. Gleichzeitig ist CI aufgrund der Erwartung von End-to-End-Tests beschäftigt, und andere Assemblys können anstehen und auf freie Agenten warten. Darüber hinaus gibt es kein klares Schema für die Arbeit mit parallelen Starts.

Die oben genannten Gründe wurden zum Anstoß für die Entwicklung von QueueRunner - einem Dienst, mit dem Sie Tests asynchron ausführen können, ohne das CI zu blockieren. Um zu arbeiten, benötigt er einen Test und Test APK sowie eine Reihe von Tests. Nachdem er die erforderlichen Daten erhalten hat, kann er Laufläufe in der Warteschlange organisieren und die erforderlichen Ressourcen zuweisen und freigeben. QueueRunner lädt die Ergebnisse des Laufs zu Jira und Stash herunter und sendet sie auch per Post und im Messenger.

QueueRunner verfügt über einen Testablauf - es überwacht den Lebenszyklus des Tests. Der Standardablauf, den wir jetzt verwenden, besteht aus fünf Schritten:

  1. Empfangsgerät. Zu diesem Zeitpunkt fordert der Geräteverwalter über den Koordinator ein reales oder virtuelles Gerät an.
  2. . APK , – , .
  3. ,

Infolgedessen bilden fünf einfache Schritte den gesamten Testlebenszyklus in unserem Service.



Welche Vorteile hat uns QueueRunner gebracht? Erstens werden alle möglichen Ressourcen maximal genutzt - es kann auf die gesamte Farm skaliert werden und schnell Ergebnisse erzielen. Zweitens hatten wir mit dem Bonus die Möglichkeit, die Reihenfolge der Tests zu steuern. Zum Beispiel können wir die längsten oder problematischsten Tests zu Beginn ausführen und so die Wartezeit auf ihre Ausführung verkürzen.

Mit QueueRunner können Sie auch intelligente Retrays erstellen. Wir speichern alle Daten in der Datenbank, sodass wir jederzeit den Verlauf des Tests sehen können. Zum Beispiel ist es möglich, das Verhältnis von erfolgreichen und nicht erfolgreichen Testdurchläufen zu betrachten und zu entscheiden, ob es sich im Prinzip lohnt, den Test neu zu starten.

QueueRunner und Devicemanager haben uns die Möglichkeit gegeben, uns an die Menge der Ressourcen anzupassen. Dank der Verwendung von Emulatoren können wir jetzt auf die gesamte Farm skalieren, dh eine nahezu unbegrenzte Anzahl virtueller Geräte gab uns die Möglichkeit, viel mehr Tests durchzuführen. Wenn die Ressourcen jedoch aus irgendeinem Grund nicht verfügbar sind, wartet der Dienst auf ihre Rückkehr und es kommt zu keinem Verlust von Starts. Dementsprechend verwenden wir nur die uns zur Verfügung stehenden Ressourcen. Nach einiger Zeit werden die Ergebnisse immer noch erhalten und gleichzeitig wird das CI nicht blockiert. Und am wichtigsten ist, dass die Testinfrastruktur und die Tests jetzt getrennt sind.
Um Tests auf Android ausführen zu können, müssen Sie uns nur noch eine Test-APK und eine Liste mit Tests geben.

Wir haben einen langen Weg von der Selenium-Farm auf virtuellen Maschinen bis zum Start von Android-Tests in der Cloud zurückgelegt. Dieser Pfad wurde jedoch noch nicht abgeschlossen.

Entwicklungsprozess


Mal sehen, wie die Testinfrastruktur mit dem Entwicklungsprozess zusammenhängt und wie Tester und Entwickler dies sehen.

Unser Android-Team verwendet den Standard-GitFlow:



Jede Funktion hat einen eigenen Zweig. Die Hauptentwicklung findet in der Entwicklungsbranche statt. Ein Entwickler, der sich entscheidet, ein neues Super-Feature zu erstellen, beginnt seine Entwicklung in einem eigenen Zweig, während andere Entwickler parallel in anderen Zweigen arbeiten können. Wenn ein Entwickler der Meinung ist, dass der ideal schöne, beste Code der Welt fertig ist und so schnell wie möglich für die Benutzer bereitgestellt werden muss, stellt er bei der Entwicklung eine Pull-Anfrage, das Gerät wird automatisch zusammengebaut, Komponententests und Komponententests werden ausgeführt. Gleichzeitig werden die APKs zusammengestellt, an QueueRunner gesendet und End-to-End-Tests ausgeführt. Danach kommen die Ergebnisse der Ausführung der Tests zum Entwickler.

Es besteht jedoch eine hohe Wahrscheinlichkeit, dass nach der Erstellung des in Entwicklung befindlichen Feature-Zweigs viele Commits aufgetreten sind. Dies bedeutet, dass die Entwicklung möglicherweise nicht mehr so ​​ist, wie sie früher war. Daher erfolgt die Vorzusammenführung zuerst - wir führen die Entwicklung zum aktuellen Feature-Zweig zusammen, und in diesem vorzeitigen Zustand erstellen wir Unit-Tests, Komponententests, End-to-End, und basierend auf diesen Ergebnissen erstellen wir einen Bericht. Daher verstehen wir, wie funktional die Funktion in der aktuellen Version von Develop ist, und wenn alles in Ordnung ist, wird sie an Benutzer gesendet.



Berichterstattung


So sieht die Stash-Berichterstellung aus:



Unser Bot schreibt zuerst, dass die Tests gestartet wurden, und wenn sie bestanden werden, aktualisiert er die Nachricht und fügt hinzu, wie viele bestanden haben, wie viele gefallen sind, wie viele bekannte Fehler und wie viele Flaky-Tests. Er schreibt dasselbe in Jira und fügt einen Link zu einem Vergleich der Starts hinzu.

So sieht der Vergleich der beiden Starts aus:



Hier wird der aktuelle Lauf im Feature-Zweig mit dem letzten Lauf in der Entwicklung verglichen. Es enthält Informationen über die Anzahl der ausgeführten Tests, Übereinstimmungsprobleme, abgebrochene Tests und instabile Flaky-Tests, die sich in einem Zustand befanden und in einen anderen gewechselt wurden.

Wenn mindestens ein Komponententest oder mehr als ein Schwellenwert für End-to-End-Tests abfällt, wird die Zusammenführung blockiert.

Um zu verstehen, ob die Tests stabil fallen, vergleichen wir die Hashes der Spuren der Stürze, bevor sie vorab von den Ziffern befreit werden, bleiben nur die Zeilennummern übrig. Wenn die Hashes übereinstimmen, ist dies der gleiche Fall. Wenn sie unterschiedlich sind, sind die Stürze höchstwahrscheinlich unterschiedlich.

Zusammenfassung


Als Ergebnis haben wir eine stabile, fehlertolerante Lösung implementiert, die sich gut an unsere Infrastruktur anpassen lässt. Dann wurde die resultierende Infrastruktur für Android-Tests angepasst. Dabei haben uns der Geräte-Manager unterstützt, der uns hilft, sowohl mit realen als auch mit virtuellen Geräten zu arbeiten, sowie QueueRunner, der uns dabei half, die Infrastruktur und die Tests zu trennen und CI für die Dauer der Tests nicht zu blockieren.

Es sah aus wie die Testlaufzeit für eine Woche im Jahr 2016 - von fünfzig Minuten oder länger.



So sieht es jetzt aus:



Diese Grafik zeigt die Läufe, die über 2 Stunden eines durchschnittlichen Geschäftstages stattgefunden haben. Die Laufzeit wurde auf maximal 15 Minuten reduziert, die Anzahl der Läufe deutlich erhöht.

All Articles