Οι δοκιμές Snapshot είναι μια μέθοδος αυτοματοποιημένου ελέγχου του περιβάλλοντος χρήστη, όπου η τρέχουσα κατάσταση της οθόνης συγκρίνεται με μια εικόνα αναφοράς (snapshot) που αποθηκεύτηκε στην προηγούμενη εκτέλεση της δοκιμής. Οποιαδήποτε οπτική απόκλιση καταγράφεται ως αλλαγή που απαιτεί επιβεβαίωση από τον προγραμματιστή. Σε αντίθεση με τις UI δοκιμές που ελέγχουν την ύπαρξη στοιχείων, οι δοκιμές snapshot εντοπίζουν αλλαγές σε επίπεδο pixel — μετατοπίσεις, χρωματικές αποκλίσεις και προβλήματα διάταξης. Σύμφωνα με τα Android Developers, 2024, οι δοκιμές Snapshot εντοπίζουν έως και 30% των οπτικών παλινδρομήσεων που χάνονται από τις παραδοσιακές UI δοκιμές, καθιστώντας τες απαραίτητο εργαλείο για τη διατήρηση συνεπούς διεπαφής.
Κύρια σημεία
Οι δοκιμές Snapshot (snapshot testing) είναι μια τεχνική κατά την οποία η δοκιμή αποδίδει ένα στοιχείο διεπαφής, αποθηκεύει την εικόνα που προκύπτει ως πρότυπο και σε επόμενες εκτελέσεις συγκρίνει την τρέχουσα απόδοση με αυτό το πρότυπο. Αν οι εικόνες ταιριάζουν — η δοκιμή περνά. Αν βρεθούν διαφορές — η δοκιμή αποτυγχάνει και ο προγραμματιστής λαμβάνει μια diff-εικόνα με επισήμανση των αλλαγμένων pixel. Η τεχνική προέρχεται από την ανάπτυξη ιστού (Jest snapshots) και έχει προσαρμοστεί για κινητές πλατφόρμες.
Η κύρια αξία των δοκιμών Snapshot είναι ο αυτόματος εντοπισμός απροσδόκητων οπτικών αλλαγών. Ένας προγραμματιστής μπορεί να αλλάξει το χρωματικό σχήμα σε ένα καθολικό θέμα και να επηρεάσει κατά λάθος δώδεκα οθόνες. Οι UI δοκιμές που ελέγχουν την ύπαρξη κουμπιών και κειμένων δεν θα το παρατηρήσουν. Μια δοκιμή Snapshot θα καταγράψει την αλλαγή κάθε pixel σε κάθε επηρεαζόμενη οθόνη, παρέχοντας μια πλήρη εικόνα της επίδρασης της αλλαγής.
Σύμφωνα με έρευνα του Mobile DevOps Summit 2023, ομάδες που χρησιμοποιούν δοκιμές Snapshot επιπλέον των κλασικών UI δοκιμών μειώνουν τον αριθμό των οπτικών ελαττωμάτων στις εκδόσεις κατά 40%. Ιδιαίτερα αποτελεσματική είναι αυτή η προσέγγιση σε έργα με συστήματα σχεδιασμού και προσέγγιση βασισμένη σε στοιχεία, όπου μια αλλαγή σε ένα βασικό στοιχείο μπορεί να επηρεάσει δεκάδες οθόνες της εφαρμογής.
Η βασική διαφορά βρίσκεται στο αντικείμενο ελέγχου. Οι UI δοκιμές ελέγχουν την ύπαρξη, την κατάσταση και τη συμπεριφορά των στοιχείων διεπαφής: «το κουμπί είναι ορατό», «το κείμενο περιέχει μήνυμα σφάλματος», «μετά το πάτημα ανοίγει νέα οθόνη». Οι δοκιμές Snapshot ελέγχουν την εμφάνιση στο σύνολό της: διάταξη στοιχείων, αποστάσεις, χρώματα, γραμματοσειρές, σκιές και στρογγυλοποιήσεις. Μια δοκιμή Snapshot απαντά στην ερώτηση «μοιάζει η οθόνη όπως αναμένεται;», ενώ μια UI δοκιμή — «λειτουργεί η οθόνη όπως αναμένεται;»
Η ταχύτητα εκτέλεσης επίσης διαφέρει. Οι UI δοκιμές εκτελούνται σε εξομοιωτή ή πραγματική συσκευή, απαιτούν πλήρη φόρτωση της εφαρμογής και διαρκούν από 10 δευτερόλεπτα έως ένα λεπτό ανά σενάριο. Οι δοκιμές Snapshot που βασίζονται σε βιβλιοθήκες όπως η Paparazzi αποδίδουν το στοιχείο σε εικονικό περιβάλλον χωρίς εκκίνηση εξομοιωτή, μειώνοντας τον χρόνο δοκιμής σε 100–500 χιλιοστά του δευτερολέπτου. Ένα πλήρες σύνολο δοκιμών Snapshot (50–100 οθόνες) εκτελείται σε 2–5 λεπτά αντί για 30–60 λεπτά για ένα αντίστοιχο σύνολο UI δοκιμών.
Ωστόσο, οι δοκιμές Snapshot δεν αντικαθιστούν τις UI δοκιμές. Η βέλτιστη στρατηγική είναι ο συνδυασμός: οι δοκιμές Snapshot καλύπτουν την οπτική παλινδρόμηση (απόδοση κάθε οθόνης σε βασικές καταστάσεις) και οι UI δοκιμές — τη συμπεριφορική (σενάρια κλικ, επικύρωση εισαγωγής, πλοήγηση). Αυτός ο συνδυασμός παρέχει 90% βεβαιότητα για την ορθότητα της διεπαφής με ελάχιστο χρόνο εκτέλεσης CI.
Στο Android, τα κύρια εργαλεία είναι τα Paparazzi και Shot. Το Paparazzi από την Cash App αποδίδει στοιχεία σε περιβάλλον δοκιμής σε JVM χωρίς εξομοιωτή, χρησιμοποιώντας τη διάταξη βαρύτητας Layoutlib. Το Shot από την Karumi εκτελεί Instrumentation-στιγμιότυπα σε πραγματική συσκευή ή εξομοιωτή και τα συγκρίνει με πρότυπα μέσω της βιβλιοθήκης AShot, λαμβάνοντας υπόψη διαφορές στην ανάλυση και την πυκνότητα pixel.
Το Paparazzi δεν απαιτεί εκκίνηση εξομοιωτή — η απόδοση γίνεται σε JVM μέσω Layoutlib, παρέχοντας ταχύτητα συγκρίσιμη με δοκιμές μονάδας. Η βιβλιοθήκη υποστηρίζει τόσο το σύστημα View όσο και το Jetpack Compose. Για το Compose χρησιμοποιείται ο τροποποιητής paparazzi.snapshot { MyComposable() }. Τα πρότυπα αποθηκεύονται στο src/test/snapshots και συγκρίνονται αυτόματα σε κάθε εκτέλεση. Το μέγιστο ποσοστό διαφοράς ρυθμίζεται μέσω maxPercentDifference.
Το SnapshotTesting από Point-Free υποστηρίζει σύγκριση όχι μόνο UIImage, αλλά και συμβολοσειρών, JSON, Data και ολόκληρων Core Data stores. Αυτό το καθιστά ένα καθολικό εργαλείο όχι μόνο για UI snapshots αλλά και για έλεγχο σειριοποίησης και αποκωδικοποίησης JSON απαντήσεων. Για SwiftUI χρησιμοποιείται η επέκταση assertSnapshot με τον τροποποιητή .image(on: .iPhone13). Η στρατηγική εγγραφής — record: true — δημιουργεί πρότυπα κατά την πρώτη εκτέλεση.
Για React Native είναι δημοφιλής η λύση react-native-testing-library σε συνδυασμό με jest-image-snapshot. Η προσέγγιση ιστού για δοκιμές Snapshot μεταφέρεται στο κινητό περιβάλλον μέσω απόδοσης στοιχείων σε περιβάλλον Node.js με επακόλουθη σύγκριση JSON-στιγμιοτύπων του εικονικού DOM. Αυτή η προσέγγιση είναι ταχύτερη από την εγγενή, αλλά λιγότερο ακριβής — δεν λαμβάνει υπόψη πλατφορμικές ιδιαιτερότητες απόδοσης γραμματοσειρών και συστημικών στοιχείων. Για Flutter χρησιμοποιούνται golden-δοκιμές μέσω του goldens toolkit.
Ας δούμε δοκιμές Snapshot για Android (Paparazzi) και iOS (SnapshotTesting). Και τα δύο παραδείγματα ελέγχουν την εμφάνιση ενός στοιχείου — μιας κάρτας χρήστη με άβαταρ, όνομα και κατάσταση. Η δοκιμή αποδίδει το στοιχείο με δεδομένα δοκιμής και συγκρίνει το αποτέλεσμα με την εικόνα αναφοράς που είναι αποθηκευμένη στο αποθετήριο.
Το Paparazzi χρησιμοποιεί την annotation @Test και τη μέθοδο snapshot() για τη λήψη απόδοσης. Τα πρότυπα αποθηκεύονται στον φάκελο src/test/snapshots και φορτώνονται αυτόματα στην επόμενη εκτέλεση για σύγκριση.
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 χρησιμοποιεί τον τροποποιητή .snapshot() μέσα στο assertSnapshot. Η βιβλιοθήκη καθορίζει αυτόματα τη μορφή — UIImage για UIView, String για κείμενο, Data για δυαδικά δεδομένα.
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))
}
}
Η τυπική ροή εργασίας περιλαμβάνει τέσσερα στάδια. Πρώτη εκτέλεση (record mode): όλες οι δοκιμές Snapshot εκτελούνται σε λειτουργία εγγραφής — οι εικόνες αναφοράς δημιουργούνται και αποθηκεύονται στο αποθετήριο. Αυτό το στάδιο εκτελείται κατά την αρχική ρύθμιση των δοκιμών ή μετά από συνειδητή αλλαγή της διεπαφής. Μετά την εγγραφή, τα πρότυπα γίνονται commit μαζί με τον κώδικα — γίνονται μέρος του έργου.
Στις επόμενες εκτελέσεις, οι δοκιμές λειτουργούν σε λειτουργία σύγκρισης: κάθε νέα απόδοση συγκρίνεται με το πρότυπο. Αν βρεθούν διαφορές, δημιουργείται μια diff-εικόνα: με πράσινο επισημαίνονται τα pixel που ταιριάζουν με το πρότυπο, με κόκκινο — αυτά που διαφέρουν. Ο προγραμματιστής μελετά το diff και αποφασίζει: αν η αλλαγή είναι αναμενόμενη (συνειδητή αλλαγή σχεδιασμού), το πρότυπο ενημερώνεται με την εντολή record· αν είναι απροσδόκητη — διορθώνεται το σφάλμα. Η ενημέρωση προτύπων γίνεται με μία εντολή: για Paparazzi είναι `./gradlew recordPaparazzi`, για SnapshotTesting — `assertSnapshot(record: true)`.
Σύμφωνα με το Spotify Engineering Blog (2022), ομάδες που χρησιμοποιούν την περιγραφόμενη ροή εργασίας ξοδεύουν κατά μέσο όρο 2 λεπτά ανά δοκιμή για ανάλυση diff-εικόνων. Με ένα σύνολο 50 δοκιμών Snapshot, ο πλήρης κύκλος ενημέρωσης προτύπων διαρκεί 15–20 λεπτά, που είναι σημαντικά ταχύτερος από τη χειροκίνητη επαλήθευση οπτικών αλλαγών σε 50 οθόνες.
Οι δοκιμές Snapshot έχουν θεμελιώδεις περιορισμούς. Ευαισθησία στο περιβάλλον: το ίδιο στοιχείο μπορεί να αποδοθεί διαφορετικά σε διαφορετικές εκδόσεις ΛΣ, πυκνότητες οθόνης και διαμορφώσεις γραμματοσειρών. Πρότυπα που δημιουργήθηκαν σε ένα μηχάνημα μπορεί να διαφέρουν από την απόδοση σε έναν CI διακομιστή. Λύση — χρήση σταθερών παραμέτρων περιβάλλοντος: συγκεκριμένη έκδοση Layoutlib για Paparazzi ή ακριβές μοντέλο συσκευής για SnapshotTesting.
Αντιμοτίβο #1: γιγάντια snapshots — μια δοκιμή Snapshot που καταγράφει ολόκληρη την οθόνη αποτυγχάνει σε κάθε ελάχιστη αλλαγή οποιουδήποτε στοιχείου. Η σωστή προσέγγιση είναι να δοκιμάζονται μεμονωμένα στοιχεία (κουμπί, κάρτα, πεδίο εισαγωγής) απομονωμένα. Κάθε στοιχείο δοκιμάζεται ανεξάρτητα, παρέχοντας ακριβή ένδειξη της πηγής της αλλαγής. Αντιμοτίβο #2: αγνόηση των diff — η αυτόματη ενημέρωση προτύπων χωρίς ανάλυση των diff-εικόνων μηδενίζει την αξία των δοκιμών Snapshot. Κάθε diff απαιτεί συνειδητή απόφαση του προγραμματιστή.
Σύμφωνα με το Better Engineering Blog (2023), οι δοκιμές Snapshot προσφέρουν τη μεγαλύτερη αξία όταν καλύπτουν στοιχεία του συστήματος σχεδιασμού και βασικές οθόνες σε βασικές καταστάσεις — κενή, συμπληρωμένη, σφάλματος και οριακή. Η κάλυψη κινήσεων και δυναμικών καταστάσεων μέσω δοκιμών Snapshot είναι αναποτελεσματική λόγω της μη ντετερμινιστικής φύσης των χρονικών σημάνσεων στην απόδοση — για τέτοια σενάρια είναι καλύτερη η εγγραφή βίντεο ή ο χειροκίνητος έλεγχος QA.
Συχνές ερωτήσεις
Όχι, οι δοκιμές Snapshot ελέγχουν την εμφάνιση, ενώ οι UI δοκιμές — τη συμπεριφορά της διεπαφής. Η βέλτιστη στρατηγική είναι ο συνδυασμός και των δύο προσεγγίσεων: snapshots για οπτική παλινδρόμηση, UI δοκιμές για έλεγχο σεναρίων και πλοήγησης. Τα snapshots απαντούν στο ερώτημα «φαίνεται σωστά;», οι UI δοκιμές — «λειτουργεί σωστά;».
Τα πρότυπα ενημερώνονται με κάθε συνειδητή αλλαγή σχεδιασμού: νέο χρώμα θέματος, αλλαγμένες αποστάσεις, προσθήκη ή αφαίρεση στοιχείων. Η ενημέρωση γίνεται μέσω λειτουργίας record, μετά την οποία οι diff-εικόνες ελέγχονται στο code review για να διασφαλιστεί ότι οι αλλαγές ανταποκρίνονται στις προσδοκίες του σχεδιαστή.
Καταρχάς τα στοιχεία του συστήματος σχεδιασμού — κουμπιά, κάρτες, πεδία εισαγωγής, modal παράθυρα. Στη συνέχεια, βασικές οθόνες σε βασικές καταστάσεις. Μην δοκιμάζετε με snapshots κινήσεις, WebView, χάρτες και οθόνες με δυναμικό περιεχόμενο — για αυτά, τα snapshots δίνουν ψευδείς αποτυχίες λόγω μη ντετερμινισμού.
Χρησιμοποιήστε το ίδιο API Level για record και test λειτουργίες. Για το Paparazzi, ορίστε συγκεκριμένη έκδοση Layoutlib στη διαμόρφωση. Για το SnapshotTesting, καθορίστε το μοντέλο συσκευής. Πρότυπα που δημιουργήθηκαν σε Android 14 μπορεί να διαφέρουν από την απόδοση σε Android 12 λόγω αλλαγών στις συστημικές γραμματοσειρές και το θέμα Material.
Στο CI, οι δοκιμές Snapshot εκτελούνται σε λειτουργία επαλήθευσης (verify). Αν μια δοκιμή αποτύχει, το CI εμφανίζει τη diff-εικόνα στα artifacts της κατασκευής. Η λειτουργία record (ενημέρωση προτύπων) εκτελείται τοπικά από τον προγραμματιστή ή σε ξεχωριστή CI διεργασία με μη αυτόματη ενεργοποίηση. Οι εικόνες αναφοράς γίνονται υποχρεωτικά commit στο αποθετήριο.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης