Snapshot testen is een methode voor geautomatiseerde controle van de gebruikersinterface, waarbij de huidige staat van het scherm wordt vergeleken met een referentiebeeld (snapshot) dat is opgeslagen bij de vorige testrun. Elke visuele afwijking wordt geregistreerd als een wijziging die bevestiging door de ontwikkelaar vereist. In tegenstelling tot UI-tests die de aanwezigheid van elementen controleren, registreren snapshot-tests pixelwijzigingen — verschuivingen, kleurafwijkingen en lay-outfouten. Volgens Android Developers, 2024, detecteert snapshot testen tot 30% van visuele regressies die door traditionele UI-tests worden gemist, wat het een onmisbaar hulpmiddel maakt voor het behouden van een consistente interface.
Hoofdpunten
Snapshot testen (snapshot testing) is een techniek waarbij de test een interfacecomponent rendert, de verkregen afbeelding als referentie opslaat en bij volgende runs de huidige render met deze referentie vergelijkt. Als de afbeeldingen overeenkomen — slaagt de test. Als er verschillen worden gevonden — faalt de test en ontvangt de ontwikkelaar een diff-afbeelding met gemarkeerde veranderde pixels. De techniek is overgenomen uit webontwikkeling (Jest snapshots) en aangepast voor mobiele platforms.
De belangrijkste waarde van snapshot-tests is automatische detectie van onverwachte visuele wijzigingen. Een ontwikkelaar kan het kleurenschema in een globaal thema wijzigen en per ongeluk tientallen schermen beïnvloeden. UI-tests die de aanwezigheid van knoppen en teksten controleren, zullen dit niet opmerken. Een snapshot-test registreert de wijziging van elke pixel op elk getroffen scherm en geeft een volledig beeld van de impact van de wijziging.
Volgens een enquête van Mobile DevOps Summit 2023 verminderen teams die snapshot testen gebruiken naast klassieke UI-tests het aantal visuele defecten in releases met 40%. Deze aanpak is bijzonder effectief in projecten met ontwerpsystemen en componentgebaseerde benaderingen, waar het wijzigen van één basiscomponent tientallen schermen van de app kan beïnvloeden.
Het fundamentele verschil zit in het object van controle. UI-tests controleren de aanwezigheid, status en het gedrag van interface-elementen: „knop is zichtbaar“, „tekst bevat een foutmelding“, „na klikken opent een nieuw scherm“. Snapshot-tests controleren het uiterlijk als geheel: plaatsing van elementen, afstanden, kleuren, lettertypen, schaduwen en afrondingen. Een snapshot-test beantwoordt de vraag „ziet het scherm eruit zoals verwacht?“, terwijl een UI-test „werkt het scherm zoals verwacht?“ beantwoordt.
De uitvoeringssnelheid verschilt ook. UI-tests worden uitgevoerd op een emulator of echt apparaat, vereisen het volledig laden van de app en duren van 10 seconden tot een minuut per scenario. Snapshot-tests op basis van bibliotheken zoals Paparazzi renderen de component in een virtuele omgeving zonder de emulator te starten, waardoor de testtijd wordt teruggebracht tot 100–500 milliseconden. Een volledige set snapshot-tests (50–100 schermen) wordt uitgevoerd in 2–5 minuten in plaats van 30–60 minuten voor een vergelijkbare set UI-tests.
Snapshot-tests vervangen echter geen UI-tests. De optimale strategie is een combinatie: snapshot-tests dekken visuele regressie (render van elk scherm in basistoestanden) en UI-tests gedragsregressie (klikscenario’s, invoervalidatie, navigatie). Deze combinatie geeft 90% vertrouwen in de correctheid van de interface met minimale CI-uitvoeringstijd.
Op Android zijn de belangrijkste tools Paparazzi en Shot. Paparazzi van Cash App rendert componenten in een testomgeving op de JVM zonder emulator, met behulp van Layoutlib gravitationele layout. Shot van Karumi maakt Instrumentation-schermafbeeldingen op een echt apparaat of emulator en vergelijkt ze met referenties via de AShot-bibliotheek, rekening houdend met verschillen in resolutie en pixel density.
Paparazzi vereist geen emulator — de render wordt uitgevoerd op de JVM via Layoutlib, wat snelheid biedt vergelijkbaar met unittesten. De bibliotheek ondersteunt zowel het View-systeem als Jetpack Compose. Voor Compose wordt de modifier paparazzi.snapshot { MyComposable() } gebruikt. Referenties worden opgeslagen in src/test/snapshots en automatisch vergeleken bij elke run. Het maximale verschilpercentage wordt geconfigureerd via maxPercentDifference.
SnapshotTesting van Point-Free ondersteunt het vergelijken van niet alleen UIImage, maar ook strings, JSON, Data en volledige Core Data-opslag. Dit maakt het een universele tool, niet alleen voor UI-snapshots, maar ook voor het controleren van serialisatie en decodering van JSON-antwoorden. Voor SwiftUI wordt de extensie assertSnapshot gebruikt met de modifier .image(on: .iPhone13). De opnamestrategie — record: true — creëert referenties bij de eerste run.
Voor React Native is de populaire oplossing react-native-testing-library in combinatie met jest-image-snapshot. De webbenadering van snapshot testen wordt overgebracht naar de mobiele omgeving door componenten te renderen in een Node.js-omgeving en vervolgens JSON-snapshots van de virtuele DOM te vergelijken. Deze benadering is sneller dan native, maar minder nauwkeurig — het houdt geen rekening met platform-specifieke kenmerken van lettertype-rendering en systeemcomponenten. Voor Flutter wordt golden testing gebruikt via de goldens toolkit.
Laten we snapshot-tests voor Android (Paparazzi) en iOS (SnapshotTesting) bekijken. Beide voorbeelden controleren het uiterlijk van een component — een gebruikerskaart met avatar, naam en status. De test rendert de component met testgegevens en vergelijkt het resultaat met het referentiebeeld dat in de repository is opgeslagen.
Paparazzi gebruikt de @Test-annotatie en de snapshot()-methode om de render vast te leggen. Referenties worden opgeslagen in de map src/test/snapshots en automatisch opgehaald bij de volgende run voor vergelijking.
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 gebruikt de modifier .snapshot() binnen assertSnapshot. De bibliotheek bepaalt automatisch het formaat — UIImage voor UIView, String voor tekst, Data voor binaire gegevens.
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))
}
}
Een typische werkstroom omvat vier fasen. Eerste run (record mode): alle snapshot-tests worden uitgevoerd in de opnamemodus — referentiebeelden worden aangemaakt en opgeslagen in de repository. Deze fase wordt uitgevoerd bij de initiële configuratie van tests of na een bewuste interfacewijziging. Na opname worden referenties samen met de code gecommit — ze worden onderdeel van het project.
Bij volgende runs werken de tests in vergelijkingsmodus: elke nieuwe render wordt vergeleken met de referentie. Als er verschillen worden gevonden, wordt een diff-afbeelding gegenereerd: pixels die overeenkomen met de referentie worden groen gemarkeerd, afwijkende pixels rood. De ontwikkelaar bestudeert de diff en neemt een beslissing: als de wijziging verwacht is (bewuste ontwerpwijziging), wordt de referentie bijgewerkt met het record-commando; als onverwacht — wordt de fout hersteld. Referenties bijwerken gebeurt met een eenmalig commando: voor Paparazzi is dat `./gradlew recordPaparazzi`, voor SnapshotTesting — `assertSnapshot(record: true)`.
Volgens Spotify Engineering Blog (2022) besteden teams die de beschreven werkstroom gebruiken gemiddeld 2 minuten per test aan het analyseren van diff-afbeeldingen. Bij een set van 50 snapshot-tests duurt de volledige cyclus van referentie-update 15–20 minuten, wat aanzienlijk sneller is dan handmatige verificatie van visuele wijzigingen op 50 schermen.
Snapshot-tests hebben fundamentele beperkingen. Gevoeligheid voor omgeving: dezelfde component kan anders worden gerenderd op verschillende OS-versies, schermdichtheden en lettertypeconfiguraties. Referenties die op één machine zijn gemaakt, kunnen afwijken van de render op de CI-server. Oplossing — het gebruik van vaste omgevingsparameters: een specifieke Layoutlib-versie voor Paparazzi of een exact apparaatmodel voor SnapshotTesting.
Antipatroon nr. 1: gigantische snapshots — een snapshot-test die het hele scherm vastlegt, faalt bij elke minimale wijziging van een component. De juiste benadering — afzonderlijke componenten (knop, kaart, invoerveld) geïsoleerd testen. Elke component wordt onafhankelijk getest, wat een precieze aanwijzing geeft van de bron van de wijziging. Antipatroon nr. 2: diff’s negeren — automatisch bijwerken van referenties zonder analyse van diff-afbeeldingen reduceert de waarde van snapshot-tests tot nul. Elke diff vereist een bewuste beslissing van de ontwikkelaar.
Volgens Better Engineering Blog (2023) bieden snapshot-tests het meeste voordeel bij het dekken van ontwerpsysteemcomponenten en belangrijke schermen in basistoestanden — leeg, gevuld, fout en grens. Het dekken van animaties en dynamische toestanden via snapshot-tests is inefficiënt vanwege de non-determinisme van tijdstempels in de render — voor dergelijke scenario’s zijn video-opname of handmatige QA-controle geschikter.
Veelgestelde vragen
Nee, snapshot-tests controleren het uiterlijk en UI-tests het gedrag van de interface. De optimale strategie is beide benaderingen combineren: snapshots voor visuele regressie, UI-tests voor het controleren van scenario’s en navigatie. Snapshots beantwoorden de vraag „ziet het er correct uit?“, UI-tests „werkt het correct?“
Referenties worden bijgewerkt bij elke bewuste ontwerpwijziging: een nieuwe themakleur, gewijzigde afstanden, toevoeging of verwijdering van elementen. Bijwerken gebeurt via de record-modus, waarna diff-afbeeldingen worden gecontroleerd tijdens code review om zeker te zijn dat wijzigingen overeenkomen met de verwachtingen van de ontwerper.
In de eerste plaats ontwerpsysteemcomponenten — knoppen, kaarten, invoervelden, modale vensters. Vervolgens belangrijke schermen in basistoestanden. Test niet met snapshots animaties, WebView, kaarten en schermen met dynamische inhoud — voor deze geven snapshots valse fouten vanwege non-determinisme.
Gebruik hetzelfde API-niveau voor record- en testmodi. Stel voor Paparazzi een specifieke Layoutlib-versie in de configuratie in. Voor SnapshotTesting fixeert u het apparaatmodel. Referenties gemaakt op Android 14 kunnen afwijken van de render op Android 12 vanwege wijzigingen in systeemlettertypen en het Material-thema.
In CI worden snapshot-tests uitgevoerd in de verificatiemodus (verify). Als een test faalt, toont CI de diff-afbeelding in de build-artefacten. De record-modus (referenties bijwerken) wordt lokaal uitgevoerd door de ontwikkelaar of in een aparte CI-taak met handmatige trigger. Referentiebeelden moeten worden gecommit in de repository.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook