Pixel Perfect UI at Scale

Wie wir UI-Tests für unser Design-Sprachsystem unter iOS verwalten.

gojek.jobs

Von Vikas und Abhinav von UX-Engineering.

Entwickler und Designer sind Entitäten, die immer versuchen, ihr Bestes zu geben, um ein schönes Produkt für die Benutzer zu entwickeln. Dies reicht jedoch nicht immer aus, um sicherzustellen, dass die sorgfältig ausgearbeiteten Designs auf dem Gerät eines Benutzers genau gleich aussehen.

Um sicherzustellen, dass Entwickler die Benutzeroberfläche in der Nähe der Entwürfe erstellen, müssen wir zuerst die Entwürfe verstehen, mit den Designern diskutieren und Zeplin nach der Entwicklung überprüfen. Wir müssen auch sicherstellen, dass die Benutzeroberfläche keine unterschiedlichen Szenarien, Codeänderungen usw. enthält.

Dies alles nimmt enorm viel Zeit in Anspruch und ist nicht immer realisierbar.

Wäre es nicht großartig, wenn wir automatisch überprüfen könnten, ob alles so ist, wie es der Designer beabsichtigt hat?

In diesem Beitrag werden wir einen Weg diskutieren, um diese Lücke zwischen entworfener und entwickelter Benutzeroberfläche effizient zu schließen. (Mit anderen Worten, die Lücke zwischen Entwicklern und Designern.)

Asphalt - Konsistenz bewahren

Seit einiger Zeit sprechen wir über unser Design-Sprachsystem Asphalt. Es wurde entwickelt, um sicherzustellen, dass unsere Benutzeroberfläche für die breite Palette von GOJEK-Produkten und -Dienstleistungen konsistent und benutzerfreundlich ist.

Fazit: Asphalt ist das Design-Sprachsystem von GO-JEK. Ziel ist es, Entwickler, Designer und alle anderen Funktionen in der gesamten Organisation zu vereinen, um Konsistenz und Effizienz zu gewährleisten. Es ist das Fundament, auf dem aufgebaut werden muss.

Asphaltkomponenten

Das Problem?

In Asphalt behandeln wir Komponenten wie wiederverwendbare Legoblöcke. Dies hilft beim schnellen Aufbau von Bauteilen.

Erstellen Sie eine Benutzeroberfläche wie Lego
Shuffle Card Legoblöcke

Dabei tendieren Entwickler dazu, die Funktionalität einer Komponente zu aktualisieren oder zu erweitern, und sie müssen in der Lage sein, dies zu tun, ohne Nebenwirkungen für andere Komponenten zu verursachen.

Wir hielten ein Überwachungssystem für erforderlich, um die UI-Komponenten bei jedem Commit / jeder Änderung, die ein Entwickler vornimmt, zu überprüfen und den Entwickler über die Auswirkungen der Änderung zu informieren. Wir mussten dieses System auch im Autopilot-Modus betreiben, unsere Komponenten häufig überprüfen und Anomalien feststellen. Das Ziel war nicht, menschliche Bediener zu ersetzen, sondern sie zu unterstützen. Dies möchten wir mit unserer UI Components Library erreichen.

Um dieses Ziel zu erreichen, haben wir uns auf Schnappschusstests festgelegt.

Schnappschuss-Test

Konzeptionell ist das Testen von Schnappschüssen eine einfache Idee: Machen Sie einen Screenshot Ihrer Benutzeroberfläche und speichern Sie ihn als Referenzbild. Machen Sie dann bei Ihren Tests Screenshots derselben Benutzeroberfläche und vergleichen Sie sie mit dem Referenzbild. Lassen Sie den Entwickler, der den Test ausführt, nach Abschluss des Tests den Unterschied und die betroffenen Änderungen erkennen und entscheiden, ob die Änderungen korrekt sind.

Implementieren von Snapshot-Tests unter iOS

Wir verwenden das im Lieferumfang von Xcode enthaltene XCUITest-Framework, mit dem Sie auf einfache Weise UI-Tests entwickeln können, die die Interaktion der Benutzer mit der Anwendung widerspiegeln.

Finden des Unterschieds zwischen zwei Screenshots

Wir verwenden Core Image-Filter (CISubtractBlendMode und CIAreaAverage), um den Unterschied zwischen einem Referenz-Screenshot und einem Screenshot des aktuellen Status einer Komponente zu ermitteln.

Im obigen Codebeispiel verwenden wir eine UIImage-Erweiterung, bei der DifferenceFilter die Hintergrundbild-Musterfarbe von der Musterfarbe des Quellbilds subtrahiert und das Differenzbild ergibt. Sowohl das Eingabe- als auch das Ausgabebild, das wir erhalten, ist ein CII-Bild.

Wir fügen das Differenzbild in AverageFilter ein, das ein Einzelpixelbild zurückgibt, das die durchschnittliche Farbe für den interessierenden Bereich enthält.

Wir führen das averageFilterOuputImage über die oben gezeigte firstPixelColor-Funktion aus. Dies gibt UIColor zurück, anhand dessen wir feststellen, ob alles schwarz ist oder nicht. Wenn es nicht vollständig schwarz ist, haben wir einen Unterschied in unserem Quellbild.

In unseren UI-Tests starten wir den Bildschirm mit der Komponente, die wir in unserer Demo-App testen müssen, und rufen diesen Image Diff-Algorithmus auf. Wenn es einen Unterschied gibt, schlagen wir den Test fehl und generieren einen Bericht mit dem Unterschied.

Beobachtung der Ergebnisse

Hier ist ein Beispiel, das wir kürzlich erlebt haben.

1) Referenz-Screenshot 2) Test-Screenshot fehlgeschlagen, der den Unterschied hervorhebt

Wir haben diese Button-Klasse, TextButton, die einen PrimaryState erhält und für die Aktualisierung ihrer Hintergrundfarben, Textfarben, des Aktivierungsstatus und des Texts verantwortlich ist. Wir haben diese Schaltfläche in den meisten unserer Ansichten in der App für primäre Interaktionen eingefügt. Wir wollten einen Teil der Rendering-Logik in der Schaltfläche ändern, haben jedoch versehentlich einen anderen Teil unserer App zerstört, von dem wir nicht wussten, dass er auch TextButton verwendet.

Wie wir auf dem Screenshot sehen können, stellte der Test fest, dass die Randfarbe zwischen den beiden Bildern unterschiedlich war, was nicht unsere Absicht war. Snapshot-Tests haben uns vor einem Fehler bewahrt, der zu Fehlern in der App bei verschiedenen Produkten und zu Verzögerungen in unserem Versandzyklus geführt hätte.

Referenz-Screenshots verwalten

Referenzbilder für TextButton

Wir halten unsere Screenshots nach Gerätetyp getrennt. Wir haben eine Namenskonvention, die device_resolution + os_version lautet. Oben sehen Sie den Ordner "640 + 1136 + 10" und Referenzbilder für TextButtons. Diese Bilder werden durch Ausführen der Tests auf dem iPhone 5s (mit einer Geräteauflösung von 640 x 1136 und iOS 10) generiert. In ähnlicher Weise generieren wir Bilder für mehrere verfügbare Geräte.

Der obige Beispielcode für ButtonTests verfügt über eine folderName-Variable und eine testPrimaryButton () -Funktion. Wenn wir den obigen Test zum ersten Mal nach dem Erstellen der Komponente ausführen, generieren wir diese Bilder aus dem Bezeichner TextButton-primary_cover_normal_state und fügen die Bildschirmabmessungen hinzu und speichern sie im Ordner ButtonTests. Auf diese Weise generieren wir Referenzbilder nur aus unserem UITest-Code. Nachdem wir diese Ordner zum Bundle hinzugefügt haben, führen wir die Tests erneut aus. Dieses Mal enthält screenshotHelper ein Referenzbild im Bundle, um sie mit dem UI-Screenshot zu vergleichen. Auf diese Weise stellen wir ein konsistentes Muster (wie die Namenskonvention, die Bündelstruktur usw.) für alle Referenzbilder sicher und beschleunigen den Prozess.

So funktioniert es?

Die Vorteile sind, dass Sie komplexe Strukturen einfach testen können, ohne viel Code schreiben zu müssen, gute Warnungen erhalten, wenn sich etwas ändert, und dass Sie diesen Test einfach aktualisieren können.

Die Nachteile sind, dass beim Lesen des Tests nicht immer klar ist, was getestet wird und wie das erwartete Verhalten ist. Manchmal ist der erstellte Schnappschuss so komplex, dass Sie falsche Annahmen übersehen, die dann zu den erwarteten Ergebnissen führen. Da es so einfach ist, Schnappschüsse zu aktualisieren, können sich falsche Dinge einschleichen.

Eine Herausforderung besteht darin, dass uns Xcode oder xcodebuildwelwelwelwel mit dem folgenden Fehler bei ungefähr Testzähler 170 begrüßt:

Warten auf die Zugänglichkeit zum Laden
Warten Sie, bis die App inaktiv ist
    App-Ereignisschleifen-Leerlaufbenachrichtigung nicht empfangen, wird fortgesetzt.
    App-Animationen vollständig Benachrichtigung nicht erhalten, wird versuchen, fortzufahren.

Nach diesem Fehler schlagen alle nachfolgenden Tests mit einer anderen Meldung fehl:

Warten auf die Eingabehilfe zum Laden Assertionsfehler: testUITests.swift: 21: Fehler beim Testen der Benutzeroberfläche - Die App-Eingabehilfe wurde nicht geladen

Wir haben uns bei Google umgesehen und hier die Details zu diesem Problem gefunden. Wir haben mehrere im Blog erwähnte Problemumgehungen ausprobiert und auch versucht, mehrere Ziele und Schemata für eine Reihe von Tests zu erstellen, aber vergebens.

Bis Apple dieses Problem endgültig behebt, wird die Anzahl der Tests auf weniger als 170 verringert.

Eine weitere Herausforderung besteht darin, die Tests schneller auszuführen. Derzeit haben wir alle Animationen deaktiviert, indem wir in der Funktion "Appdelegate didFinishLaunchingWithOptions" Folgendes festgelegt haben. Durch Deaktivieren aller Animationen werden die Tests zuverlässiger und schneller ausgeführt.

wenn ProcessInfo.processInfo.arguments.contains ("AsphaltUITests") {
UIView.setAnimationsEnabled (false)
}

Schuppentests bedürfen ebenfalls einer besonderen Erwähnung. Mit der Erweiterung unserer Testsuite stellen wir uns dem Problem, dass ein oder zwei Tests zufällig fehlschlagen. Um diese Art von Fehlverhalten zu beheben, experimentieren wir derzeit mit der Fastlane-Plugin-Test-Center-Multi-Scan-Funktion, um nur diese fragilen Tests erneut auszuführen. Dies erspart uns Zeit, die für die erneute Ausführung der gesamten Suite erforderlich ist.

Hinweis: Diese Funktion unterstützt noch keine Xcode-Paralleltests, ist hier jedoch WIP-fähig.

Wie geht es beim Snapshot-Testen weiter?

Wir planen, UITest-Klassen durch Analyse der UIComponents-Klassen automatisch zu generieren. Meistens ist der Code für die UITest-Klasse dumm. Greifen Sie auf die Komponente zu, machen Sie einen Screenshot der der Ansicht zugewiesenen Eingabehilfen-ID und ordnen Sie sie dem Referenzbild zu (dessen Dateiname durch den Klassennamen generiert wird). Unser erster Schritt besteht darin, diese dummen Schritte automatisch zu generieren und dann nach oben zu gehen, um Schablonen für verschiedene Verhaltensweisen von Komponenten wie Wischen, Schaltflächen, Maximaldaten, Minimaldaten und vielem mehr zu schreiben. Natürlich können wir mit diesem Prozess nicht alle Testszenarien abdecken. Dadurch werden jedoch viele allgemeine Testszenarien entfernt, die wir für eine UIComponent schreiben.

In Summe

Insgesamt haben Snapshot-Tests gute Dienste geleistet, aber ein Tool ist nur so gut wie sein Benutzer. Es ist daher wichtig, dass Sie sie granular und lesbar gestalten und so behandeln, als würden Sie normale Tests durchführen. Die Kombination dieser Aspekte mit einer Kultur gründlicher Code-Überprüfungen hat sich für uns ausgezahlt.

Ich hoffe, Sie erhalten ein klares Bild davon, was Schnappschusstests sind und wie sie als Teil einer umfassenden Teststrategie verwendet werden können.

Als Designer und Entwickler müssen immer Dinge verbessert und Prozesse optimiert werden. Bei GOJEK erkennen wir dies und versuchen es mit einer Kultur der engen Zusammenarbeit zwischen Teams zu überwinden. Wenn Sie zu dieser Kultur beitragen können, besuchen Sie gojek.jobs - wir suchen kluge Köpfe, die uns beim Aufbau der SuperApp in Südostasien unterstützen

gojek.jobs