Il test di snapshot è un metodo di verifica automatizzata dell'interfaccia utente in cui lo stato corrente dello schermo viene confrontato con un'immagine di riferimento (snapshot) salvata durante l'esecuzione precedente del test. Qualsiasi discrepanza visiva viene registrata come una modifica che richiede la conferma dello sviluppatore. A differenza dei test dell'interfaccia utente che verificano la presenza di elementi, i test di snapshot rilevano cambiamenti a livello di pixel: spostamenti, deviazioni di colore e problemi di layout. Secondo Android Developers, 2024, il test di snapshot rileva fino al 30% delle regressioni visive che sfuggono ai test dell'interfaccia utente tradizionali, rendendolo uno strumento indispensabile per mantenere un'interfaccia coerente.
Punti chiave
Il test di snapshot è una tecnica in cui un test renderizza un componente dell'interfaccia, salva l'immagine risultante come riferimento e nelle esecuzioni successive confronta il rendering corrente con questo riferimento. Se le immagini corrispondono, il test supera. Se vengono trovate differenze, il test fallisce e lo sviluppatore riceve un'immagine diff con i pixel modificati evidenziati. La tecnica è presa in prestito dallo sviluppo web (snapshot di Jest) e adattata per le piattaforme mobili.
Il valore principale dei test di snapshot è la rilevazione automatica di cambiamenti visivi inaspettati. Uno sviluppatore potrebbe modificare la combinazione di colori in un tema globale e influenzare accidentalmente una dozzina di schermate. I test dell'interfaccia utente che verificano la presenza di pulsanti e testi non lo noteranno. Un test di snapshot catturerà ogni cambiamento di pixel su ogni schermata interessata, fornendo un quadro completo dell'impatto della modifica.
Secondo il sondaggio del Mobile DevOps Summit 2023, i team che utilizzano i test di snapshot in aggiunta ai test dell'interfaccia utente classici riducono il numero di difetti visivi nei rilasci del 40%. Questo approccio è particolarmente efficace in progetti con sistemi di design e architetture basate su componenti, dove la modifica di un componente base può influenzare dozzine di schermate dell'applicazione.
La differenza fondamentale risiede nell'oggetto di verifica. I test dell'interfaccia utente verificano la presenza, lo stato e il comportamento degli elementi dell'interfaccia: “il pulsante è visibile”, “il testo contiene un messaggio di errore”, “dopo la pressione si apre una nuova schermata”. I test di snapshot verificano l'aspetto visivo completo: posizionamento degli elementi, margini, colori, caratteri, ombre e angoli arrotondati. Un test di snapshot risponde alla domanda “la schermata appare come previsto?”, mentre un test dell'interfaccia utente risponde a “la schermata funziona come previsto?”
Anche la velocità di esecuzione differisce. I test dell'interfaccia utente vengono eseguiti su un emulatore o dispositivo reale, richiedono il caricamento completo dell'applicazione e impiegano da 10 secondi a un minuto per scenario. I test di snapshot con librerie come Paparazzi renderizzano i componenti in un ambiente virtuale senza avviare un emulatore, riducendo il tempo di test a 100–500 millisecondi. Un set completo di test di snapshot (50–100 schermate) viene eseguito in 2–5 minuti invece di 30–60 minuti per un set comparabile di test dell'interfaccia utente.
Tuttavia, i test di snapshot non sostituiscono i test dell'interfaccia utente. La strategia ottimale è una combinazione: i test di snapshot coprono la regressione visiva (rendering di ogni schermata negli stati di base), mentre i test dell'interfaccia utente coprono gli aspetti comportamentali (scenari di clic, validazione degli input, navigazione). Questa combinazione fornisce il 90% di fiducia nella correttezza dell'interfaccia con un tempo di esecuzione CI minimo.
Su Android, gli strumenti principali sono Paparazzi e Shot. Paparazzi di Cash App renderizza i componenti in un ambiente di test JVM senza emulatore, utilizzando il layout a gravità Layoutlib. Shot di Karumi esegue screenshot di Instrumentation su un dispositivo reale o emulatore e li confronta con i riferimenti tramite la libreria AShot, tenendo conto delle differenze di risoluzione e densità di pixel.
Paparazzi non richiede l'avvio di un emulatore — il rendering viene eseguito sulla JVM tramite Layoutlib, offrendo una velocità paragonabile ai test unitari. La libreria supporta sia il sistema View che Jetpack Compose. Per Compose, utilizzare il modificatore paparazzi.snapshot { MyComposable() }. I riferimenti sono memorizzati in src/test/snapshots e vengono confrontati automaticamente a ogni esecuzione. La percentuale massima di differenza è configurabile tramite maxPercentDifference.
SnapshotTesting di Point-Free supporta il confronto non solo di UIImage, ma anche di stringhe, JSON, Data e interi store Core Data. Questo la rende uno strumento versatile non solo per snapshot dell'interfaccia utente, ma anche per verificare la serializzazione e decodifica di risposte JSON. Per SwiftUI, utilizzare l'estensione assertSnapshot con il modificatore .image(on: .iPhone13). La strategia record: true crea riferimenti al primo avvio.
Per React Native, la soluzione popolare è react-native-testing-library combinata con jest-image-snapshot. L'approccio web ai test di snapshot viene trasferito nell'ambiente mobile renderizzando i componenti in un ambiente Node.js, seguito dal confronto degli snapshot JSON del DOM virtuale. Questo approccio è più veloce di quello nativo, ma meno preciso — non tiene conto delle particolarità di rendering dei caratteri e dei componenti di sistema specifici della piattaforma. Per Flutter, viene utilizzato il test golden attraverso il toolkit goldens.
Esaminiamo i test di snapshot per Android (Paparazzi) e iOS (SnapshotTesting). Entrambi gli esempi verificano l'aspetto di un componente — una scheda utente con avatar, nome e stato. Il test renderizza il componente con dati di test e confronta il risultato con un'immagine di riferimento memorizzata nel repository.
Paparazzi utilizza l'annotazione @Test e il metodo snapshot() per catturare il rendering. I riferimenti vengono salvati nella cartella src/test/snapshots e caricati automaticamente all'esecuzione successiva per il confronto.
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 utilizza il modificatore .snapshot() all'interno di assertSnapshot. La libreria determina automaticamente il formato — UIImage per UIView, String per il testo, Data per i dati binari.
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))
}
}
Un flusso di lavoro tipico include quattro fasi. Prima esecuzione (modalità record): tutti i test di snapshot vengono eseguiti in modalità di registrazione — le immagini di riferimento vengono create e salvate nel repository. Questa fase viene eseguita durante la configurazione iniziale dei test o dopo una modifica intenzionale dell'interfaccia. Dopo la registrazione, i riferimenti vengono committati insieme al codice — diventano parte del progetto.
Nelle esecuzioni successive, i test funzionano in modalità di confronto: ogni nuovo rendering viene confrontato con il riferimento. Se vengono trovate differenze, viene generata un'immagine diff: i pixel che corrispondono al riferimento vengono evidenziati in verde, quelli diversi in rosso. Lo sviluppatore studia il diff e prende una decisione: se la modifica è prevista (cambiamento di design consapevole), il riferimento viene aggiornato con il comando record; se inaspettata, il bug viene corretto. L'aggiornamento dei riferimenti viene eseguito con un singolo comando: per Paparazzi è `./gradlew recordPaparazzi`, per SnapshotTesting — `assertSnapshot(record: true)`.
Secondo il Spotify Engineering Blog (2022), i team che utilizzano il flusso di lavoro descritto dedicano in media 2 minuti per test all'analisi delle immagini diff. Con un set di 50 test di snapshot, un ciclo completo di aggiornamento dei riferimenti richiede 15–20 minuti, significativamente più veloce della verifica manuale dei cambiamenti visivi su 50 schermate.
I test di snapshot hanno limitazioni fondamentali. Sensibilità all'ambiente: lo stesso componente può essere renderizzato in modo diverso su diverse versioni del sistema operativo, densità di schermo e configurazioni di caratteri. I riferimenti creati su una macchina possono differire dal rendering su un server CI. La soluzione è utilizzare parametri di ambiente fissi: una versione specifica di Layoutlib per Paparazzi o un modello di dispositivo esatto per SnapshotTesting.
Anti-pattern n. 1: snapshot giganti — un test di snapshot che cattura l'intero schermo fallisce a ogni minima modifica di qualsiasi componente. L'approccio corretto è testare i singoli componenti (pulsante, scheda, campo di input) in modo isolato. Ogni componente viene testato indipendentemente, fornendo un'identificazione precisa della fonte del cambiamento. Anti-pattern n. 2: ignorare i diff — aggiornare automaticamente i riferimenti senza analizzare le immagini diff riduce il valore dei test di snapshot a zero. Ogni diff richiede una decisione consapevole dello sviluppatore.
Secondo il Better Engineering Blog (2023), i test di snapshot offrono il massimo valore quando coprono i componenti del sistema di design e le schermate chiave negli stati di base — vuoto, riempito, errore e limite. Coprire animazioni e stati dinamici con i test di snapshot è inefficiente a causa della natura non deterministica dei timestamp nel rendering — per tali scenari, la registrazione video o il controllo manuale di qualità sono più adatti.
Domande frequenti
No, i test di snapshot verificano l'aspetto visivo, mentre i test dell'interfaccia utente verificano il comportamento dell'interfaccia. La strategia ottimale è combinare entrambi gli approcci: snapshot per la regressione visiva, test dell'interfaccia utente per scenari e navigazione. Gli snapshot rispondono a “appare corretto?”, i test dell'interfaccia utente a “funziona correttamente?”.
I riferimenti vengono aggiornati a ogni modifica di design consapevole: un nuovo colore del tema, margini modificati, aggiunta o rimozione di elementi. L'aggiornamento viene eseguito tramite la modalità record, dopo di che le immagini diff vengono esaminate nella revisione del codice per garantire che le modifiche corrispondano alle aspettative del designer.
In primo luogo, i componenti del sistema di design — pulsanti, schede, campi di input, finestre modali. Poi le schermate chiave negli stati di base. Non testate con snapshot animazioni, WebView, mappe e schermate con contenuto dinamico — gli snapshot producono falsi fallimenti a causa del non determinismo.
Utilizzate lo stesso livello API per le modalità record e test. Per Paparazzi, specificate una versione particolare di Layoutlib nella configurazione. Per SnapshotTesting, fissate il modello del dispositivo. I riferimenti creati su Android 14 possono differire dal rendering su Android 12 a causa di modifiche nei caratteri di sistema e nel tema Material.
In CI, i test di snapshot vengono eseguiti in modalità di verifica (verify). Se un test fallisce, il CI mostra l'immagine diff negli artefatti di build. La modalità record (aggiornamento dei riferimenti) viene eseguita localmente dallo sviluppatore o in un'attività CI separata con attivazione manuale. Le immagini di riferimento devono essere committate nel repository.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche