Snapshot-Testing ist eine Methode zur automatisierten Überprüfung der Benutzeroberfläche, bei der der aktuelle Bildschirmzustand mit einem Referenzbild (Snapshot) verglichen wird, das beim vorherigen Testlauf gespeichert wurde. Jede visuelle Abweichung wird als Änderung erfasst, die eine Bestätigung durch den Entwickler erfordert. Im Gegensatz zu UI-Tests, die das Vorhandensein von Elementen prüfen, erkennen Snapshottests pixelgenaue Veränderungen — Verschiebungen, Farbabweichungen und Layoutprobleme. Laut Android Developers, 2024 erkennt Snapshot-Testing bis zu 30 % der visuellen Regressionen, die von herkömmlichen UI-Tests übersehen werden, was es zu einem unverzichtbaren Werkzeug für die Aufrechterhaltung einer konsistenten Oberfläche macht.
Wichtige Punkte
Snapshot-Testing ist eine Technik, bei der ein Test eine UI-Komponente rendert, das resultierende Bild als Referenz speichert und bei späteren Ausführungen die aktuelle Darstellung mit dieser Referenz vergleicht. Stimmen die Bilder überein — besteht der Test. Werden Unterschiede festgestellt — schlägt der Test fehl, und der Entwickler erhält ein Diff-Bild mit hervorgehobenen geänderten Pixeln. Die Technik wurde aus der Webentwicklung (Jest Snapshots) übernommen und für mobile Plattformen adaptiert.
Der Hauptwert von Snapshottests liegt in der automatischen Erkennung unerwarteter visueller Änderungen. Ein Entwickler könnte das Farbschema in einem globalen Thema ändern und versehentlich ein Dutzend Bildschirme beeinflussen. UI-Tests, die das Vorhandensein von Schaltflächen und Texten prüfen, bemerken dies nicht. Ein Snapshottest erfasst jede Pixeländerung auf jedem betroffenen Bildschirm und liefert ein vollständiges Bild der Auswirkungen der Änderung.
Laut einer Umfrage des Mobile DevOps Summit 2023 reduzieren Teams, die Snapshottests zusätzlich zu klassischen UI-Tests einsetzen, die Anzahl visueller Fehler in Releases um 40 %. Dieser Ansatz ist besonders effektiv in Projekten mit Designsystemen und komponentenbasierten Architekturen, wo die Änderung einer Basiskomponente Dutzende von Anwendungsbildschirmen betreffen kann.
Der grundlegende Unterschied liegt im Prüfobjekt. UI-Tests überprüfen das Vorhandensein, den Zustand und das Verhalten von Oberflächenelementen: „Die Schaltfläche ist sichtbar“, „Der Text enthält eine Fehlermeldung“, „Nach dem Drücken öffnet sich ein neuer Bildschirm“. Snapshottests prüfen das gesamte visuelle Erscheinungsbild: Elementpositionierung, Abstände, Farben, Schriftarten, Schatten und abgerundete Ecken. Ein Snapshottest beantwortet die Frage „Sieht der Bildschirm wie erwartet aus?“, während ein UI-Test die Frage beantwortet „Funktioniert der Bildschirm wie erwartet?“
Auch die Ausführungsgeschwindigkeit unterscheidet sich. UI-Tests laufen auf einem Emulator oder echten Gerät, erfordern ein vollständiges Laden der Anwendung und dauern 10 Sekunden bis eine Minute pro Szenario. Snapshottests mit Bibliotheken wie Paparazzi rendern Komponenten in einer virtuellen Umgebung ohne Emulatorstart, was die Testzeit auf 100–500 Millisekunden reduziert. Ein vollständiger Satz von Snapshottests (50–100 Bildschirme) wird in 2–5 Minuten ausgeführt, statt 30–60 Minuten für einen vergleichbaren Satz von UI-Tests.
Snapshottests ersetzen jedoch keine UI-Tests. Die optimale Strategie ist eine Kombination: Snapshottests decken visuelle Regressionen ab (Darstellung jedes Bildschirms in Grundzuständen), während UI-Tests die verhaltensbezogenen Aspekte abdecken (Klickszenarien, Eingabevalidierung, Navigation). Diese Kombination bietet 90 % Vertrauen in die Korrektheit der Oberfläche bei minimaler CI-Laufzeit.
Auf Android sind die wichtigsten Werkzeuge Paparazzi und Shot. Paparazzi von Cash App rendert Komponenten in einer JVM-Testumgebung ohne Emulator und verwendet das Gravity-Layout von Layoutlib. Shot von Karumi erstellt Instrumentation-Screenshots auf einem echten Gerät oder Emulator und vergleicht sie über die Bibliothek AShot mit Referenzen, wobei Unterschiede in Auflösung und Pixeldichte berücksichtigt werden.
Paparazzi benötigt keinen Emulatorstart — das Rendering erfolgt auf der JVM über Layoutlib, was eine mit Unit-Tests vergleichbare Geschwindigkeit bietet. Die Bibliothek unterstützt sowohl das View-System als auch Jetpack Compose. Für Compose wird der Modifikator paparazzi.snapshot { MyComposable() } verwendet. Referenzen werden in src/test/snapshots gespeichert und bei jedem Lauf automatisch verglichen. Der maximale Unterschiedsprozentsatz wird über maxPercentDifference konfiguriert.
SnapshotTesting von Point-Free unterstützt den Vergleich nicht nur von UIImage, sondern auch von Strings, JSON, Data und ganzen Core Data Stores. Dies macht es zu einem vielseitigen Werkzeug nicht nur für UI-Snapshots, sondern auch für die Überprüfung der Serialisierung und Dekodierung von JSON-Antworten. Für SwiftUI wird die assertSnapshot-Erweiterung mit dem Modifikator .image(on: .iPhone13) verwendet. Die Strategie record: true erstellt Referenzen beim ersten Lauf.
Für React Native ist die beliebte Lösung react-native-testing-library in Kombination mit jest-image-snapshot. Der Web-Ansatz für Snapshottests wird in die mobile Umgebung übertragen, indem Komponenten in einer Node.js-Umgebung gerendert und anschließend JSON-Snapshots des virtuellen DOM verglichen werden. Dieser Ansatz ist schneller als nativer Code, aber weniger genau — er berücksichtigt keine plattformspezifischen Schriftartendarstellungen und Systemkomponenteneigenschaften. Für Flutter wird Golden-Testing über das goldens-Toolkit verwendet.
Betrachten wir Snapshottests für Android (Paparazzi) und iOS (SnapshotTesting). Beide Beispiele überprüfen das Erscheinungsbild einer Komponente — einer Benutzerkarte mit Avatar, Name und Status. Der Test rendert die Komponente mit Testdaten und vergleicht das Ergebnis mit einem im Repository gespeicherten Referenzbild.
Paparazzi verwendet die @Test-Annotation und die snapshot()-Methode, um das Rendering zu erfassen. Referenzen werden im Ordner src/test/snapshots gespeichert und beim nächsten Lauf automatisch zum Vergleich geladen.
class UserCardSnapshotTest {
@get:Rule
val paparazzi = Paparazzi(
theme = "Theme.MyApp",
maxPercentDifference = 0.1
)
@Test
fun userCard_defaultState() {
val card = UserCard(
name = "Alice Johnson",
status = "Online",
avatarUrl = "https://example.com/avatar.png"
)
paparazzi.snapshot(card)
}
@Test
fun userCard_offlineState() {
val card = UserCard(
name = "Bob Smith",
status = "Offline",
avatarUrl = null
)
paparazzi.snapshot(card, name = "user_card_offline")
}
}
SnapshotTesting verwendet den .snapshot()-Modifikator innerhalb von assertSnapshot. Die Bibliothek bestimmt automatisch das Format — UIImage für UIView, String für Text, Data für Binärdaten.
import SnapshotTesting
import XCTest
class UserCardSnapshotTests: XCTestCase {
func testUserCardDefaultState() {
let card = UserCardView(
name: "Alice Johnson",
status: "Online",
avatarURL: URL(string: "https://example.com/avatar.png")
)
let controller = UIHostingController(rootView: card)
assertSnapshot(matching: controller, as: .image(on: .iPhone13))
}
func testUserCardOfflineState() {
let card = UserCardView(
name: "Bob Smith",
status: "Offline",
avatarURL: nil
)
assertSnapshot(matching: card, as: .image(on: .iPhone13))
}
}
Ein typischer Workflow umfasst vier Phasen. Erster Lauf (Aufnahmemodus): Alle Snapshottests werden im Aufnahmemodus ausgeführt — Referenzbilder werden erstellt und im Repository gespeichert. Diese Phase wird bei der ersten Testeinrichtung oder nach einer bewussten Änderung der Oberfläche durchgeführt. Nach der Aufnahme werden die Referenzen zusammen mit dem Code committet — sie werden Teil des Projekts.
Bei späteren Läufen arbeiten die Tests im Vergleichsmodus: Jede neue Darstellung wird mit der Referenz verglichen. Werden Unterschiede festgestellt, wird ein Diff-Bild generiert: Pixel, die mit der Referenz übereinstimmen, werden grün hervorgehoben, abweichende Pixel rot. Der Entwickler untersucht das Diff und entscheidet: Ist die Änderung erwartet (bewusste Designänderung), wird die Referenz mit dem Aufnahmebefehl aktualisiert; ist sie unerwartet, wird der Fehler behoben. Die Aktualisierung von Referenzen erfolgt mit einem einzigen Befehl: Für Paparazzi ist es `./gradlew recordPaparazzi`, für SnapshotTesting — `assertSnapshot(record: true)`.
Laut dem Spotify Engineering Blog (2022) verbringen Teams, die den beschriebenen Workflow verwenden, durchschnittlich 2 Minuten pro Test mit der Analyse von Diff-Bildern. Bei einem Satz von 50 Snapshottests dauert ein vollständiger Referenzaktualisierungszyklus 15–20 Minuten, was deutlich schneller ist als die manuelle Überprüfung visueller Änderungen auf 50 Bildschirmen.
Snapshottests haben grundlegende Einschränkungen. Umgebungsempfindlichkeit: Dieselbe Komponente kann auf verschiedenen OS-Versionen, Bildschirmdichten und Schriftkonfigurationen unterschiedlich dargestellt werden. Auf einem Rechner erstellte Referenzen können sich von der Darstellung auf einem CI-Server unterscheiden. Die Lösung besteht darin, feste Umgebungsparameter zu verwenden: eine bestimmte Layoutlib-Version für Paparazzi oder ein exaktes Gerätemodell für SnapshotTesting.
Antipattern Nr. 1: Riesige Snapshots — ein Snapshottest, der den gesamten Bildschirm erfasst, schlägt bei jeder kleinsten Änderung an einer beliebigen Komponente fehl. Der richtige Ansatz ist, einzelne Komponenten (Schaltfläche, Karte, Eingabefeld) isoliert zu testen. Jede Komponente wird unabhängig getestet, was eine genaue Identifizierung der Änderungsquelle ermöglicht. Antipattern Nr. 2: Diffs ignorieren — das automatische Aktualisieren von Referenzen ohne Analyse der Diff-Bilder reduziert den Wert von Snapshottests auf null. Jedes Diff erfordert eine bewusste Entscheidung des Entwicklers.
Laut dem Better Engineering Blog (2023) bieten Snapshottests den größten Nutzen, wenn sie Komponenten des Designsystems und wichtige Bildschirme in Grundzuständen abdecken — leer, gefüllt, Fehler und Grenzfall. Das Abdecken von Animationen und dynamischen Zuständen durch Snapshottests ist aufgrund der nicht deterministischen Natur von Zeitstempeln beim Rendering ineffizient — für solche Szenarien sind Videoaufzeichnungen oder manuelle QA-Prüfungen besser geeignet.
Häufig gestellte Fragen
Nein, Snapshottests prüfen das visuelle Erscheinungsbild, während UI-Tests das Verhalten der Oberfläche prüfen. Die optimale Strategie ist die Kombination beider Ansätze: Snapshots für visuelle Regression, UI-Tests für Szenarien und Navigation. Snapshots beantworten „Sieht es richtig aus?“, UI-Tests beantworten „Funktioniert es richtig?“.
Referenzen werden bei jeder bewussten Designänderung aktualisiert: neue Themenfarbe, geänderte Abstände, Hinzufügen oder Entfernen von Elementen. Die Aktualisierung erfolgt über den Aufnahmemodus, wonach die Diff-Bilder im Code-Review überprüft werden, um sicherzustellen, dass die Änderungen den Erwartungen des Designers entsprechen.
In erster Linie Komponenten des Designsystems — Schaltflächen, Karten, Eingabefelder, modale Fenster. Dann wichtige Bildschirme in Grundzuständen. Testen Sie nicht mit Snapshots Animationen, WebView, Karten und Bildschirme mit dynamischem Inhalt — Snapshots erzeugen hier aufgrund von Nichtdeterminismus falsche Fehlschläge.
Verwenden Sie dieselbe API-Stufe für Aufnahme- und Testmodus. Für Paparazzi geben Sie eine bestimmte Layoutlib-Version in der Konfiguration an. Für SnapshotTesting legen Sie das Gerätemodell fest. Auf Android 14 erstellte Referenzen können sich aufgrund von Änderungen in Systemschriftarten und dem Material-Design von der Darstellung auf Android 12 unterscheiden.
In CI laufen Snapshottests im Überprüfungsmodus (verify). Schlägt ein Test fehl, zeigt CI das Diff-Bild in den Build-Artefakten an. Der Aufnahmemodus (Referenzaktualisierung) wird lokal vom Entwickler oder in einer separaten CI-Aufgabe mit manuellem Trigger ausgeführt. Referenzbilder müssen in das Repository committet werden.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch