Entwicklung von CI im mobilen Entwicklungsteam

Heutzutage werden die meisten Softwareprodukte in Teams entwickelt. Die Voraussetzungen für eine erfolgreiche Teamentwicklung lassen sich in Form eines einfachen Diagramms darstellen.

Entwicklung von CI im mobilen Entwicklungsteam

Nachdem Sie Ihren Code geschrieben haben, müssen Sie sicherstellen, dass er:

  1. Es funktioniert
  2. Es macht nichts kaputt, auch nicht den Code, den Ihre Kollegen geschrieben haben.

Wenn beide Voraussetzungen erfüllt sind, sind Sie auf dem Weg zum Erfolg. Um diese Bedingungen einfach zu überprüfen und nicht vom profitablen Weg abzuweichen, haben wir Continuous Integration entwickelt.

CI ist ein Workflow, bei dem Sie Ihren Code so oft wie möglich in den gesamten Produktcode integrieren. Und man integriert nicht nur, sondern überprüft auch ständig, ob alles funktioniert. Da Sie viele und häufige Kontrollen durchführen müssen, lohnt es sich, über eine Automatisierung nachzudenken. Sie können alles manuell überprüfen, sollten es aber nicht, und hier erfahren Sie, warum.

  • liebe Leute. Eine Arbeitsstunde eines Programmierers ist teurer als eine Arbeitsstunde eines Servers.
  • Menschen machen Fehler. Daher kann es vorkommen, dass Tests auf dem falschen Zweig ausgeführt wurden oder der falsche Commit für Tester kompiliert wurde.
  • Die Leute sind faul. Von Zeit zu Zeit, wenn ich eine Aufgabe erledigt habe, kommt mir der Gedanke: „Was gibt es zu überprüfen?“ Ich habe zwei Zeilen geschrieben - alles funktioniert! Ich denke, einige von euch haben auch manchmal solche Gedanken. Aber Sie sollten immer nachsehen.

Wie Continuous Integration im mobilen Entwicklungsteam von Avito implementiert und entwickelt wurde, wie sie von 0 auf 450 Builds pro Tag gestiegen sind und dass Build-Maschinen 200 Stunden am Tag zusammengebaut werden, sagt Nikolai Nesterov (nnesterov) ist an allen evolutionären Veränderungen der CI/CD-Android-Anwendung beteiligt.

Die Geschichte basiert auf dem Beispiel eines Android-Befehls, die meisten Ansätze sind jedoch auch auf iOS anwendbar.


Es war einmal eine Person, die im Avito Android-Team arbeitete. Per Definition brauchte er nichts von Continuous Integration: Es gab niemanden, mit dem er sich integrieren konnte.

Doch die Anwendung wuchs, immer mehr neue Aufgaben kamen hinzu und das Team wuchs entsprechend. Irgendwann ist es an der Zeit, einen Code-Integrationsprozess formeller zu etablieren. Es wurde beschlossen, Git Flow zu verwenden.

Entwicklung von CI im mobilen Entwicklungsteam

Das Konzept des Git-Flows ist bekannt: Ein Projekt verfügt über einen gemeinsamen Entwicklungszweig, und für jede neue Funktion schneiden Entwickler einen separaten Zweig, übernehmen diesen, pushen ihn und öffnen einen, wenn sie ihren Code in den Entwicklungszweig einbinden möchten Pull-Anfrage. Um Wissen auszutauschen und Ansätze zu diskutieren, haben wir Code Review eingeführt, das heißt, Kollegen müssen den Code des anderen überprüfen und bestätigen.

Überprüfungen

Code mit den Augen zu sehen ist cool, aber nicht genug. Daher werden automatische Kontrollen eingeführt.

  • Zunächst prüfen wir ARK-Montage.
  • Viel Junit-Tests.
  • Wir betrachten Codeabdeckung, da wir Tests durchführen.

Um zu verstehen, wie diese Prüfungen durchgeführt werden sollten, schauen wir uns den Entwicklungsprozess in Avito an.

Schematisch lässt es sich so darstellen:

  • Ein Entwickler schreibt Code auf seinem Laptop. Hier können Sie Integrationsprüfungen ausführen – entweder mit einem Commit-Hook oder einfach im Hintergrund.
  • Nachdem der Entwickler den Code gepusht hat, öffnet er einen Pull-Request. Damit der Code in den Entwicklungszweig aufgenommen werden kann, muss eine Codeüberprüfung durchgeführt und die erforderliche Anzahl an Bestätigungen gesammelt werden. Hier können Sie Prüfungen und Builds aktivieren: Bis alle Builds erfolgreich sind, kann der Pull-Request nicht zusammengeführt werden.
  • Nachdem die Pull-Anfrage zusammengeführt und der Code in die Entwicklung eingebunden wurde, können Sie einen geeigneten Zeitpunkt wählen: zum Beispiel nachts, wenn alle Server frei sind, und so viele Prüfungen durchführen, wie Sie möchten.

Niemand mochte es, Scans auf seinem Laptop durchzuführen. Wenn ein Entwickler ein Feature fertiggestellt hat, möchte er es schnell pushen und einen Pull-Request öffnen. Wenn in diesem Moment einige lange Prüfungen gestartet werden, ist das nicht nur nicht sehr angenehm, sondern verlangsamt auch die Entwicklung: Während der Laptop etwas prüft, ist es unmöglich, normal daran zu arbeiten.

Uns hat es sehr gefallen, die Kontrollen nachts durchzuführen, weil man dort viel Zeit hat und die Server herumlaufen können. Aber wenn der Feature-Code in die Entwicklung geht, ist der Entwickler leider viel weniger motiviert, die von CI gefundenen Fehler zu beheben. Ich ertappte mich immer wieder bei dem Gedanken, als ich mir alle im Morgenbericht gefundenen Fehler ansah, dass ich sie eines Tages später beheben würde, denn jetzt gibt es in Jira eine coole neue Aufgabe, mit der ich einfach anfangen möchte.

Wenn Prüfungen eine Pull-Anfrage blockieren, ist genügend Motivation vorhanden, denn bis die Builds grün werden, gelangt der Code nicht in die Entwicklung, was bedeutet, dass die Aufgabe nicht abgeschlossen wird.

Aus diesem Grund haben wir die folgende Strategie gewählt: Wir führen nachts die größtmögliche Anzahl an Prüfungen durch und starten die kritischsten und vor allem die schnellsten Prüfungen auf einer Pull-Anfrage. Doch damit nicht genug: Parallel dazu optimieren wir die Geschwindigkeit der Prüfungen, um sie vom Nachtmodus auf Pull-Request-Prüfungen umzustellen.

Zu diesem Zeitpunkt waren alle unsere Builds recht schnell abgeschlossen, sodass wir einfach den ARK-Build, Junit-Tests und Code-Coverage-Berechnungen als Blocker für den Pull-Request eingebunden haben. Wir haben es aktiviert, darüber nachgedacht und die Codeabdeckung aufgegeben, weil wir dachten, dass wir sie nicht brauchen.

Wir haben zwei Tage gebraucht, um das grundlegende CI vollständig einzurichten (im Folgenden ist die geschätzte Zeit ungefähr und wird für die Skalierung benötigt).

Danach begannen wir weiterzudenken – überprüfen wir überhaupt richtig? Führen wir Builds auf Pull-Requests korrekt aus?

Wir haben den Build mit dem letzten Commit des Zweigs gestartet, von dem aus die Pull-Anfrage geöffnet wurde. Tests dieses Commits können jedoch nur zeigen, dass der vom Entwickler geschriebene Code funktioniert. Aber sie beweisen nicht, dass er nichts kaputt gemacht hat. Tatsächlich müssen Sie den Status des Entwicklungszweigs überprüfen, nachdem eine Funktion darin zusammengeführt wurde.

Entwicklung von CI im mobilen Entwicklungsteam

Dazu haben wir ein einfaches Bash-Skript geschrieben premerge.sh:

#!/usr/bin/env bash

set -e

git fetch origin develop

git merge origin/develop

Hier werden einfach alle neuesten Änderungen aus der Entwicklung abgerufen und in den aktuellen Zweig eingefügt. Wir haben das Skript premerge.sh als ersten Schritt in allen Builds hinzugefügt und begonnen, genau zu prüfen, was wir wollen Integration.

Es dauerte drei Tage, das Problem zu lokalisieren, eine Lösung zu finden und dieses Skript zu schreiben.

Die Anwendung entwickelte sich, immer mehr Aufgaben erschienen, das Team wuchs und premerge.sh begann uns manchmal im Stich zu lassen. Bei Develop gab es widersprüchliche Änderungen, die den Build zum Scheitern brachten.

Ein Beispiel dafür, wie das passiert:

Entwicklung von CI im mobilen Entwicklungsteam

Zwei Entwickler beginnen gleichzeitig mit der Arbeit an Features A und B. Der Entwickler von Feature A entdeckt ein ungenutztes Feature im Projekt answer() und wie ein guter Pfadfinder entfernt er es. Gleichzeitig fügt der Entwickler von Feature B in seinem Zweig einen neuen Aufruf dieser Funktion hinzu.

Entwickler beenden ihre Arbeit und öffnen gleichzeitig einen Pull-Request. Die Builds werden gestartet, premerge.sh überprüft beide Pull-Requests auf den neuesten Entwicklungsstand – alle Prüfungen sind grün. Danach wird die Pull-Anfrage von Feature A zusammengeführt, die Pull-Anfrage von Feature B wird zusammengeführt ... Boom! Die Entwicklung bricht ab, weil der Entwicklungscode einen Aufruf einer nicht vorhandenen Funktion enthält.

Entwicklung von CI im mobilen Entwicklungsteam

Wenn es sich nicht entwickelt, dann ist es so lokale Katastrophe. Das gesamte Team kann nichts sammeln und zum Testen einreichen.

Zufälligerweise habe ich am häufigsten an Infrastrukturaufgaben gearbeitet: Analyse, Netzwerk, Datenbanken. Das heißt, ich habe die Funktionen und Klassen geschrieben, die andere Entwickler verwenden. Aus diesem Grund befand ich mich sehr oft in ähnlichen Situationen. Ich hatte dieses Bild sogar eine Zeit lang hängen.

Entwicklung von CI im mobilen Entwicklungsteam

Da uns dies nicht zusagte, begannen wir mit der Suche nach Möglichkeiten, dies zu verhindern.

Wie man die Entwicklung nicht kaputt macht

Die erste Option: Erstellen Sie alle Pull-Anfragen neu, wenn Sie die Entwicklung aktualisieren. Wenn in unserem Beispiel der Pull-Request mit Feature A als erster in die Entwicklung einbezogen wird, wird der Pull-Request von Feature B neu erstellt und dementsprechend schlagen die Prüfungen aufgrund eines Kompilierungsfehlers fehl.

Um zu verstehen, wie lange dies dauern wird, betrachten Sie ein Beispiel mit zwei PRs. Wir eröffnen zwei PRs: zwei Builds, zwei Prüfläufe. Nachdem die erste PR mit der Entwicklung zusammengeführt wurde, muss die zweite neu erstellt werden. Insgesamt erfordern zwei PRs drei Prüfdurchläufe: 2 + 1 = 3.

Im Prinzip ist es in Ordnung. Aber wir haben uns die Statistiken angesehen, und die typische Situation in unserem Team war 10 offene PRs, und dann ist die Anzahl der Prüfungen die Summe der Progression: 10 + 9 + ... + 1 = 55. Das heißt, 10 zu akzeptieren PRs, Sie müssen 55 Mal neu erstellen. Und dies im Idealfall, wenn alle Prüfungen beim ersten Mal erfolgreich sind und niemand eine zusätzliche Pull-Anfrage öffnet, während diese Dutzend verarbeitet werden.

Stellen Sie sich vor, Sie wären ein Entwickler, der als Erster auf die Schaltfläche „Zusammenführen“ klicken muss, denn wenn ein Nachbar dies tut, müssen Sie warten, bis alle Builds erneut durchlaufen wurden ... Nein, das wird nicht funktionieren , es wird die Entwicklung ernsthaft verlangsamen.

Zweiter möglicher Weg: Sammeln Sie Pull-Anfragen nach der Codeüberprüfung. Das heißt, Sie öffnen einen Pull-Request, holen die erforderliche Anzahl an Genehmigungen von Kollegen ein, korrigieren, was erforderlich ist, und starten dann die Builds. Wenn sie erfolgreich sind, wird die Pull-Anfrage in die Entwicklung integriert. In diesem Fall gibt es keine weiteren Neustarts, allerdings wird die Rückmeldung stark verlangsamt. Wenn ich als Entwickler einen Pull-Request öffne, möchte ich sofort sehen, ob er funktioniert. Wenn beispielsweise ein Test fehlschlägt, müssen Sie ihn schnell beheben. Bei einem verzögerten Build verlangsamt sich das Feedback und damit die gesamte Entwicklung. Auch das hat uns nicht gepasst.

Infolgedessen blieb nur die dritte Option – Fahrrad. Unser gesamter Code und alle unsere Quellen werden in einem Repository auf dem Bitbucket-Server gespeichert. Dementsprechend mussten wir ein Plugin für Bitbucket entwickeln.

Entwicklung von CI im mobilen Entwicklungsteam

Dieses Plugin überschreibt den Pull-Request-Zusammenführungsmechanismus. Der Anfang ist Standard: Die PR wird geöffnet, alle Assemblys werden gestartet, die Codeüberprüfung ist abgeschlossen. Aber nachdem die Codeüberprüfung abgeschlossen ist und der Entwickler beschließt, auf „Zusammenführen“ zu klicken, prüft das Plugin, anhand welchem ​​Entwicklungsstatus die Prüfungen durchgeführt wurden. Wenn die Entwicklung nach den Builds aktualisiert wurde, lässt das Plugin nicht zu, dass ein solcher Pull-Request in den Hauptzweig eingebunden wird. Es werden einfach die Builds einer relativ neuen Entwicklung neu gestartet.

Entwicklung von CI im mobilen Entwicklungsteam

In unserem Beispiel mit widersprüchlichen Änderungen schlagen solche Builds aufgrund eines Kompilierungsfehlers fehl. Dementsprechend muss der Entwickler von Feature B den Code korrigieren, die Prüfungen neu starten, dann wendet das Plugin automatisch die Pull-Anfrage an.

Vor der Implementierung dieses Plugins haben wir durchschnittlich 2,7 Überprüfungsläufe pro Pull-Anfrage durchgeführt. Mit dem Plugin gab es 3,6 Starts. Das hat uns gepasst.

Es ist erwähnenswert, dass dieses Plugin einen Nachteil hat: Es startet den Build nur einmal neu. Das heißt, es gibt immer noch ein kleines Fenster, durch das widersprüchliche Änderungen entstehen können. Die Wahrscheinlichkeit dafür ist jedoch gering, und wir haben diesen Kompromiss zwischen der Anzahl der Starts und der Wahrscheinlichkeit eines Scheiterns geschlossen. In zwei Jahren hat es nur einmal gefeuert, es war also wahrscheinlich nicht umsonst.

Wir haben zwei Wochen gebraucht, um die erste Version des Bitbucket-Plugins zu schreiben.

Neue Schecks

In der Zwischenzeit wuchs unser Team weiter. Neue Schecks wurden hinzugefügt.

Wir dachten: Warum Fehler machen, wenn man sie verhindern kann? Und deshalb haben sie es umgesetzt Statische Code-Analyse. Wir haben mit Lint begonnen, das im Android SDK enthalten ist. Aber zu diesem Zeitpunkt wusste er überhaupt nicht, wie man mit Kotlin-Code arbeitet, und wir hatten bereits 75 % der Anwendung in Kotlin geschrieben. Daher wurden eingebaute Flusen hinzugefügt Android Studio prüft.

Um dies zu erreichen, mussten wir einiges pervertieren: Wir nahmen Android Studio, packten es in Docker und führten es auf CI mit einem virtuellen Monitor aus, sodass es denkt, es liefe auf einem echten Laptop. Aber es hat funktioniert.

In dieser Zeit haben wir auch angefangen, viel zu schreiben Instrumentierungstests und umgesetzt Screenshot-Test. Hierbei wird ein Referenz-Screenshot für eine separate kleine Ansicht erstellt und der Test besteht darin, einen Screenshot der Ansicht zu erstellen und ihn Pixel für Pixel direkt mit dem Standard zu vergleichen. Wenn es eine Diskrepanz gibt, bedeutet das, dass irgendwo ein Fehler im Layout oder in den Stilen aufgetreten ist.

Aber Instrumentierungstests und Screenshot-Tests müssen auf Geräten ausgeführt werden: auf Emulatoren oder auf realen Geräten. Da es viele Tests gibt und diese häufig durchgeführt werden, ist eine ganze Farm erforderlich. Die Gründung einer eigenen Farm ist zu arbeitsintensiv, daher haben wir eine fertige Option gefunden – Firebase Test Lab.

Firebase-Testlabor

Es wurde ausgewählt, weil Firebase ein Google-Produkt ist, was bedeutet, dass es zuverlässig sein und wahrscheinlich nie sterben wird. Die Preise sind angemessen: 5 $ pro Betriebsstunde eines echten Geräts, 1 $ pro Betriebsstunde eines Emulators.

Die Implementierung des Firebase Test Lab in unser CI dauerte etwa drei Wochen.

Aber das Team wuchs weiter und Firebase begann uns leider im Stich zu lassen. Zu diesem Zeitpunkt hatte er kein SLA. Manchmal ließ uns Firebase warten, bis die erforderliche Anzahl an Geräten für Tests frei war, und begann nicht sofort mit der Ausführung, wie wir es wollten. Das Warten in der Schlange dauerte bis zu einer halben Stunde, was eine sehr lange Zeit ist. Bei jedem PR wurden Instrumentierungstests durchgeführt, Verzögerungen verlangsamten die Entwicklung erheblich und dann kam die monatliche Rechnung mit einer runden Summe. Im Allgemeinen wurde beschlossen, Firebase aufzugeben und intern zu arbeiten, da das Team ausreichend gewachsen war.

Docker + Python + Bash

Wir haben Docker genommen, Emulatoren hineingestopft und ein einfaches Programm in Python geschrieben, das zum richtigen Zeitpunkt die erforderliche Anzahl von Emulatoren in der richtigen Version startet und sie bei Bedarf stoppt. Und natürlich ein paar Bash-Skripte – was wären wir ohne sie?

Die Erstellung unserer eigenen Testumgebung dauerte fünf Wochen.

Infolgedessen gab es für jede Pull-Anfrage eine umfangreiche Liste von Prüfungen, die die Zusammenführung blockieren:

  • ARK-Versammlung;
  • Junit-Tests;
  • Fussel;
  • Android Studio prüft;
  • Instrumentierungstests;
  • Screenshot-Tests.

Dies verhinderte viele mögliche Ausfälle. Technisch hat alles funktioniert, die Entwickler bemängelten jedoch, dass das Warten auf Ergebnisse zu lange dauerte.

Wie lang ist zu lang? Wir haben Daten von Bitbucket und TeamCity in das Analysesystem hochgeladen und das festgestellt durchschnittliche Wartezeit 45 Minuten. Das heißt, ein Entwickler wartet beim Öffnen einer Pull-Anfrage durchschnittlich 45 Minuten auf die Build-Ergebnisse. Meiner Meinung nach ist das viel, und so kann man nicht arbeiten.

Natürlich haben wir uns entschieden, alle unsere Builds zu beschleunigen.

Beschleunigen wir

Da Builds oft in einer Warteschlange stehen, ist das Erste, was wir tun habe mehr Hardware gekauft — Umfangreiche Entwicklung ist am einfachsten. Die Warteschlangen für Builds hörten auf, die Wartezeit verringerte sich jedoch nur geringfügig, da einige Überprüfungen selbst sehr lange dauerten.

Entfernen von Schecks, die zu lange dauern

Unsere kontinuierliche Integration könnte diese Art von Fehlern und Problemen erkennen.

  • Das werde ich nicht tun. CI kann einen Kompilierungsfehler abfangen, wenn etwas aufgrund widersprüchlicher Änderungen nicht erstellt werden kann. Wie gesagt, dann kann niemand mehr etwas zusammenbauen, die Entwicklung stoppt und alle werden nervös.
  • Fehler im Verhalten. Zum Beispiel, wenn die Anwendung erstellt wird, aber abstürzt, wenn Sie eine Taste drücken, oder wenn die Taste überhaupt nicht gedrückt wird. Das ist schlecht, weil ein solcher Fehler den Benutzer erreichen kann.
  • Fehler im Layout. Beispielsweise wurde auf eine Schaltfläche geklickt, diese wurde jedoch um 10 Pixel nach links verschoben.
  • Anstieg der technischen Schulden.

Nachdem wir uns diese Liste angesehen hatten, stellten wir fest, dass nur die ersten beiden Punkte kritisch sind. Wir wollen solche Probleme zuerst erkennen. Fehler im Layout werden bereits in der Entwurfsprüfungsphase entdeckt und können dann leicht behoben werden. Der Umgang mit technischen Schulden erfordert einen separaten Prozess und eine separate Planung, daher haben wir uns entschieden, dies nicht auf einer Pull-Anfrage zu testen.

Basierend auf dieser Klassifizierung haben wir die gesamte Liste der Prüfungen durcheinander gebracht. Durchgestrichener Lint und verschob den Start über Nacht: Nur damit ein Bericht darüber erstellt werden konnte, wie viele Probleme es bei dem Projekt gab. Wir haben vereinbart, separat mit den technischen Schulden zu arbeiten, und Auf Android Studio-Prüfungen wurde komplett verzichtet. Android Studio in Docker zum Ausführen von Inspektionen klingt interessant, verursacht aber große Probleme beim Support. Jedes Update auf Android Studio-Versionen bedeutet einen Kampf mit unverständlichen Fehlern. Außerdem war es schwierig, Screenshot-Tests zu unterstützen, da die Bibliothek nicht sehr stabil war und es zu Fehlalarmen kam. Screenshot-Tests wurden aus der Checkliste entfernt.

Als Ergebnis blieb uns Folgendes übrig:

  • ARK-Versammlung;
  • Junit-Tests;
  • Instrumentierungstests.

Gradle-Remote-Cache

Ohne schwere Kontrollen wurde alles besser. Aber der Perfektion sind keine Grenzen gesetzt!

Unsere Anwendung war bereits in etwa 150 Gradle-Module aufgeteilt. Der Gradle-Remote-Cache funktioniert in diesem Fall normalerweise gut, daher haben wir beschlossen, es auszuprobieren.

Gradle Remote Cache ist ein Dienst, der Build-Artefakte für einzelne Aufgaben in einzelnen Modulen zwischenspeichern kann. Anstatt den Code tatsächlich zu kompilieren, greift Gradle über HTTP auf den Remote-Cache zu und fragt, ob diese Aufgabe bereits von jemandem ausgeführt wurde. Wenn ja, wird einfach das Ergebnis heruntergeladen.

Das Ausführen des Gradle-Remote-Cache ist einfach, da Gradle ein Docker-Image bereitstellt. Das haben wir in drei Stunden geschafft.

Sie mussten lediglich Docker starten und eine Zeile in das Projekt schreiben. Obwohl es schnell auf den Markt gebracht werden kann, wird es ziemlich lange dauern, bis alles gut funktioniert.

Unten sehen Sie das Diagramm der Cache-Fehler.

Entwicklung von CI im mobilen Entwicklungsteam

Zu Beginn lag der Prozentsatz der Cache-Miss bei etwa 65. Nach drei Wochen gelang es uns, diesen Wert auf 20 % zu steigern. Es stellte sich heraus, dass die Aufgaben, die die Android-Anwendung sammelt, seltsame transitive Abhängigkeiten aufweisen, weshalb Gradle den Cache übersehen hat.

Durch die Verbindung des Caches haben wir den Build erheblich beschleunigt. Doch neben der Montage fallen auch Instrumentierungstests an, die viel Zeit in Anspruch nehmen. Möglicherweise müssen nicht alle Tests für jede Pull-Anfrage ausgeführt werden. Um das herauszufinden, nutzen wir die Wirkungsanalyse.

Einflussanalyse

Bei einer Pull-Anfrage sammeln wir Git Diff und finden die modifizierten Gradle-Module.

Entwicklung von CI im mobilen Entwicklungsteam

Es ist sinnvoll, nur Instrumentierungstests durchzuführen, die die geänderten Module und alle davon abhängigen Module überprüfen. Es macht keinen Sinn, Tests für benachbarte Module durchzuführen: Der Code dort hat sich nicht geändert und es kann nichts kaputt gehen.

Instrumentierungstests sind nicht so einfach, da sie sich im Anwendungsmodul der obersten Ebene befinden müssen. Wir haben Heuristiken mit Bytecode-Analyse verwendet, um zu verstehen, zu welchem ​​Modul jeder Test gehört.

Es dauerte etwa acht Wochen, den Betrieb der Instrumentierungstests so zu verbessern, dass nur die beteiligten Module getestet wurden.

Maßnahmen zur Beschleunigung der Inspektionen haben sich bewährt. Von 45 Minuten sind wir auf etwa 15 gestiegen. Es ist schon normal, eine Viertelstunde auf einen Build zu warten.

Aber jetzt beschweren sich Entwickler darüber, dass sie nicht verstehen, welche Builds gestartet werden, wo das Protokoll angezeigt wird, warum der Build rot ist, welcher Test fehlgeschlagen ist usw.

Entwicklung von CI im mobilen Entwicklungsteam

Probleme mit dem Feedback verlangsamen die Entwicklung, daher haben wir versucht, möglichst klare und detaillierte Informationen zu jeder PR und jedem Build bereitzustellen. Wir begannen mit Kommentaren in Bitbucket zur PR, in denen angegeben wurde, welcher Build fehlgeschlagen war und warum, und schrieben gezielte Nachrichten in Slack. Am Ende haben wir ein PR-Dashboard für die Seite erstellt mit einer Liste aller Builds, die derzeit ausgeführt werden, und deren Status: in der Warteschlange, in Ausführung, abgestürzt oder abgeschlossen. Sie können auf den Build klicken und zu seinem Protokoll gelangen.

Entwicklung von CI im mobilen Entwicklungsteam

Sechs Wochen wurden für detailliertes Feedback aufgewendet.

Pläne

Kommen wir zur jüngeren Geschichte. Nachdem wir das Feedback-Problem gelöst hatten, erreichten wir ein neues Niveau – wir beschlossen, unsere eigene Emulatorfarm aufzubauen. Wenn es viele Tests und Emulatoren gibt, ist es schwierig, diese zu verwalten. Infolgedessen wurden alle unsere Emulatoren mit flexibler Ressourcenverwaltung in den k8s-Cluster verschoben.

Darüber hinaus gibt es weitere Pläne.

  • Flusen zurückgeben (und andere statische Analysen). Wir arbeiten bereits in diese Richtung.
  • Führen Sie alles auf einem PR-Blocker aus End-to-End-Tests auf allen SDK-Versionen.

Daher haben wir die Entwicklungsgeschichte der kontinuierlichen Integration in Avito verfolgt. Nun möchte ich einige Ratschläge aus erfahrener Sicht geben.

Tipps

Wenn ich nur einen Rat geben könnte, wäre es dieser:

Bitte seien Sie vorsichtig mit Shell-Skripten!

Bash ist ein sehr flexibles und leistungsstarkes Tool, mit dem sich sehr bequem und schnell Skripte schreiben lassen. Aber man kann damit in eine Falle tappen, und leider sind auch wir in diese Falle getappt.

Alles begann mit einfachen Skripten, die auf unseren Build-Maschinen ausgeführt wurden:

#!/usr/bin/env bash
./gradlew assembleDebug

Aber wie Sie wissen, entwickelt sich alles mit der Zeit und wird immer komplizierter – lassen Sie uns ein Skript von einem anderen ausführen, übergeben wir dort einige Parameter – am Ende mussten wir eine Funktion schreiben, die bestimmt, auf welcher Ebene der Bash-Verschachtelung wir uns jetzt in Ordnung befinden um die notwendigen Anführungszeichen einzufügen, um alles in Gang zu bringen.

Entwicklung von CI im mobilen Entwicklungsteam

Sie können sich den Arbeitsaufwand für die Entwicklung solcher Skripte vorstellen. Ich rate Ihnen, nicht in diese Falle zu tappen.

Was kann ersetzt werden?

  • Jede Skriptsprache. Schreiben Sie an Python- oder Kotlin-Skript bequemer, weil es sich um Programmierung und nicht um Skripte handelt.
  • Oder beschreiben Sie die gesamte Build-Logik im Formular Benutzerdefinierte Gradle-Aufgaben für Ihr Projekt.

Wir haben uns für die zweite Option entschieden und löschen jetzt systematisch alle Bash-Skripte und schreiben viele benutzerdefinierte Gradle-Aufgaben.

Tipp Nr. 2: Speichern Sie die Infrastruktur im Code.

Es ist praktisch, wenn die Continuous Integration-Einstellung nicht in der UI-Schnittstelle von Jenkins oder TeamCity usw. gespeichert wird, sondern in Form von Textdateien direkt im Projekt-Repository. Dies gibt Versionierbarkeit. Es wird nicht schwierig sein, ein Rollback durchzuführen oder den Code in einem anderen Zweig zu erstellen.

Skripte können in einem Projekt gespeichert werden. Was tun mit der Umwelt?

Tipp Nr. 3: Docker kann bei der Umgebung helfen.

Es wird Android-Entwicklern auf jeden Fall helfen; iOS hat leider noch keins.

Dies ist ein Beispiel für eine einfache Docker-Datei, die JDK und Android-SDK enthält:

FROM openjdk:8

ENV SDK_URL="https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip" 
    ANDROID_HOME="/usr/local/android-sdk" 
    ANDROID_VERSION=26 
    ANDROID_BUILD_TOOLS_VERSION=26.0.2

# Download Android SDK
RUN mkdir "$ANDROID_HOME" .android 
    && cd "$ANDROID_HOME" 
    && curl -o sdk.zip $SDK_URL 
    && unzip sdk.zip 
    && rm sdk.zip 
    && yes | $ANDROID_HOME/tools/bin/sdkmanager --licenses

# Install Android Build Tool and Libraries
RUN $ANDROID_HOME/tools/bin/sdkmanager --update
RUN $ANDROID_HOME/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" 
    "platforms;android-${ANDROID_VERSION}" 
    "platform-tools"

RUN mkdir /application
WORKDIR /application

Nachdem Sie diese Docker-Datei geschrieben (ich verrate Ihnen ein Geheimnis, Sie müssen sie nicht schreiben, sondern einfach fertig von GitHub ziehen) und das Image zusammengestellt haben, erhalten Sie eine virtuelle Maschine, auf der Sie die Anwendung erstellen können und Junit-Tests ausführen.

Die beiden Hauptgründe, warum dies sinnvoll ist, sind Skalierbarkeit und Wiederholbarkeit. Mit Docker können Sie schnell ein Dutzend Build-Agenten erstellen, die genau die gleiche Umgebung wie der vorherige haben. Dies erleichtert das Leben von CI-Ingenieuren erheblich. Es ist ziemlich einfach, das Android-SDK in Docker zu laden, aber mit Emulatoren ist es etwas schwieriger: Sie müssen etwas härter arbeiten (oder das fertige SDK erneut von GitHub herunterladen).

Tipp Nr. 4: Vergessen Sie nicht, dass Inspektionen nicht um der Inspektion willen, sondern für Menschen durchgeführt werden.

Schnelles und vor allem klares Feedback ist für Entwickler sehr wichtig: Was ist kaputt gegangen, welcher Test ist fehlgeschlagen, wo kann ich das Buildlog einsehen?

Tipp Nr. 5: Seien Sie pragmatisch bei der Entwicklung von Continuous Integration.

Machen Sie sich klar, welche Arten von Fehlern Sie verhindern möchten und wie viel Ressourcen, Zeit und Computerzeit Sie bereit sind, dafür aufzuwenden. Kontrollen, die zu lange dauern, können beispielsweise über Nacht verschoben werden. Und diejenigen von ihnen, die nicht sehr wichtige Fehler erkennen, sollten vollständig aufgegeben werden.

Tipp Nr. 6: Verwenden Sie vorgefertigte Tools.

Mittlerweile gibt es viele Unternehmen, die Cloud CI anbieten.

Entwicklung von CI im mobilen Entwicklungsteam

Dies ist eine gute Lösung für kleine Teams. Sie müssen nichts unterstützen, zahlen nur ein wenig Geld, erstellen Ihre Anwendung und führen sogar Instrumentierungstests durch.

Tipp Nr. 7: In einem großen Team sind Inhouse-Lösungen profitabler.

Aber früher oder später, wenn das Team wächst, werden Inhouse-Lösungen profitabler. Bei diesen Entscheidungen gibt es ein Problem. In der Wirtschaft gibt es ein Gesetz der sinkenden Rendite: Bei jedem Projekt wird jede weitere Verbesserung immer schwieriger und erfordert immer mehr Investitionen.

Die Wirtschaftswissenschaften beschreiben unser gesamtes Leben, einschließlich der kontinuierlichen Integration. Ich habe einen Zeitplan mit den Arbeitskosten für jede Entwicklungsstufe unserer kontinuierlichen Integration erstellt.

Entwicklung von CI im mobilen Entwicklungsteam

Es ist klar, dass jede Verbesserung immer schwieriger wird. Wenn Sie sich diese Grafik ansehen, können Sie erkennen, dass die kontinuierliche Integration entsprechend der wachsenden Teamgröße entwickelt werden muss. Für ein Team aus zwei Personen ist es eine mittelmäßige Idee, 50 Tage damit zu verbringen, eine interne Emulatorfarm zu entwickeln. Aber gleichzeitig ist es für ein großes Team auch eine schlechte Idee, überhaupt keine kontinuierliche Integration durchzuführen, da Integrationsprobleme, die Verbesserung der Kommunikation usw. es wird noch länger dauern.

Wir begannen mit der Idee, dass Automatisierung notwendig ist, weil Menschen teuer sind, Fehler machen und faul sind. Aber Menschen automatisieren auch. Daher gelten für die Automatisierung dieselben Probleme.

  • Automatisierung ist teuer. Denken Sie an den Arbeitsplan.
  • Wenn es um Automatisierung geht, machen Menschen Fehler.
  • Manchmal ist es sehr faul, etwas zu automatisieren, weil alles so funktioniert. Warum noch etwas verbessern, warum all diese kontinuierliche Integration?

Aber ich habe Statistiken: Fehler werden in 20 % der Baugruppen erkannt. Und das liegt nicht daran, dass unsere Entwickler schlecht Code schreiben. Dies liegt daran, dass Entwickler davon überzeugt sind, dass Fehler, die ihnen passieren, nicht in der Entwicklung landen, sondern durch automatische Prüfungen aufgedeckt werden. Dementsprechend können Entwickler mehr Zeit damit verbringen, Code und interessante Dinge zu schreiben, anstatt etwas lokal auszuführen und zu testen.

Üben Sie kontinuierliche Integration. Aber in Maßen.

Nikolai Nesterov hält übrigens nicht nur selbst tolle Reportagen, sondern ist auch Mitglied im Programmkomitee AppsConf und hilft anderen, bedeutungsvolle Reden für Sie vorzubereiten. Die Vollständigkeit und Nützlichkeit des nächsten Konferenzprogramms kann anhand der Themen beurteilt werden zeitlicher Ablauf. Weitere Informationen finden Sie am 22. und 23. April im Infospace.

Source: habr.com

Kommentar hinzufügen